Major refactoring (#86)

Major code refactoring.
This commit is contained in:
jeefo 2019-07-01 21:10:00 +03:00
commit 68f2f010fd
24 changed files with 8524 additions and 8166 deletions

View file

@ -9,327 +9,295 @@
#pragma once
static constexpr int MAXBUF = 4096, PADDING = 18, THRESHOLD = 2, NIL = MAXBUF;
// see https://github.com/encode84/ulz/
class FastLZ final : NonCopyable {
public:
static constexpr int EXCESS = 16;
static constexpr int WINDOW_BITS = 17;
static constexpr int WINDOW_SIZE = cr::bit (WINDOW_BITS);
static constexpr int WINDOW_MASK = WINDOW_SIZE - 1;
class Compress {
protected:
unsigned long int m_csize;
static constexpr int MIN_MATCH = 4;
static constexpr int MAX_CHAIN = cr::bit (5);
uint8 m_buffer[MAXBUF + PADDING - 1];
int m_matchPos;
int m_matchLen;
int m_left[MAXBUF + 1];
int m_right[MAXBUF + 257];
int m_parent[MAXBUF + 1];
static constexpr int HASH_BITS = 19;
static constexpr int HASH_SIZE = cr::bit (HASH_BITS);
static constexpr int NIL = -1;
static constexpr int UNCOMPRESS_RESULT_FAILED = -1;
private:
void initTrees (void) {
for (int i = MAXBUF + 1; i <= MAXBUF + 256; i++) {
m_right[i] = NIL;
int *m_hashTable;
int *m_prevTable;
public:
FastLZ (void) {
m_hashTable = new int[HASH_SIZE];
m_prevTable = new int[WINDOW_SIZE];
}
for (int j = 0; j < MAXBUF; j++) {
m_parent[j] = NIL;
}
}
void insert (int node) {
int i;
int compare = 1;
uint8 *key = &m_buffer[node];
int temp = MAXBUF + 1 + key[0];
m_right[node] = m_left[node] = NIL;
m_matchLen = 0;
for (;;) {
if (compare >= 0) {
if (m_right[temp] != NIL) {
temp = m_right[temp];
}
else {
m_right[temp] = node;
m_parent[node] = temp;
return;
}
}
else {
if (m_left[temp] != NIL) {
temp = m_left[temp];
}
else {
m_left[temp] = node;
m_parent[node] = temp;
return;
}
}
for (i = 1; i < PADDING; i++) {
if ((compare = key[i] - m_buffer[temp + i]) != 0) {
break;
}
}
if (i > m_matchLen) {
m_matchPos = temp;
if ((m_matchLen = i) >= PADDING) {
break;
}
}
}
m_parent[node] = m_parent[temp];
m_left[node] = m_left[temp];
m_right[node] = m_right[temp];
m_parent[m_left[temp]] = node;
m_parent[m_right[temp]] = node;
if (m_right[m_parent[temp]] == temp) {
m_right[m_parent[temp]] = node;
}
else {
m_left[m_parent[temp]] = node;
}
m_parent[temp] = NIL;
}
void erase (int node) {
int temp;
if (m_parent[node] == NIL) {
return; // not in tree
}
if (m_right[node] == NIL) {
temp = m_left[node];
}
else if (m_left[node] == NIL) {
temp = m_right[node];
}
else {
temp = m_left[node];
if (m_right[temp] != NIL) {
do {
temp = m_right[temp];
} while (m_right[temp] != NIL);
m_right[m_parent[temp]] = m_left[temp];
m_parent[m_left[temp]] = m_parent[temp];
m_left[temp] = m_left[node];
m_parent[m_left[node]] = temp;
}
m_right[temp] = m_right[node];
m_parent[m_right[node]] = temp;
}
m_parent[temp] = m_parent[node];
if (m_right[m_parent[node]] == node) {
m_right[m_parent[node]] = temp;
}
else {
m_left[m_parent[node]] = temp;
}
m_parent[node] = NIL;
~FastLZ (void) {
delete [] m_hashTable;
delete [] m_prevTable;
}
public:
Compress (void) : m_csize (0), m_matchPos (0), m_matchLen (0) {
memset (m_right, 0, sizeof (m_right));
memset (m_left, 0, sizeof (m_left));
memset (m_parent, 0, sizeof (m_parent));
memset (m_buffer, 0, sizeof (m_buffer));
int compress (uint8 *in, int inLength, uint8 *out) {
for (int i = 0; i < HASH_SIZE; i++) {
m_hashTable[i] = NIL;
}
uint8 *op = out;
int anchor = 0;
int cur = 0;
while (cur < inLength) {
const int maxMatch = inLength - cur;
int bestLength = 0;
int dist = 0;
if (maxMatch >= MIN_MATCH) {
const int limit = cr::max (cur - WINDOW_SIZE, NIL);
int chainLength = MAX_CHAIN;
int lookup = m_hashTable[hash32 (&in[cur])];
while (lookup > limit) {
if (in[lookup + bestLength] == in[cur + bestLength] && load32 (&in[lookup]) == load32 (&in[cur])) {
int length = MIN_MATCH;
while (length < maxMatch && in[lookup + length] == in[cur + length]) {
length++;
}
~Compress (void) = default;
if (length > bestLength) {
bestLength = length;
dist = cur - lookup;
int encode_ (const char *fileName, uint8 *header, int headerSize, uint8 *buffer, int bufferSize) {
File fp (fileName, "wb");
if (!fp.isValid ()) {
return -1;
if (length == maxMatch) {
break;
}
}
int i, length, node, ptr, last, cbp, bp = 0;
uint8 cb[17] = {0, }, mask, bit;
fp.write (header, headerSize, 1);
initTrees ();
cb[0] = 0;
cbp = mask = 1;
ptr = 0;
node = MAXBUF - PADDING;
for (i = ptr; i < node; i++)
m_buffer[i] = ' ';
for (length = 0; (length < PADDING) && (bp < bufferSize); length++) {
bit = buffer[bp++];
m_buffer[node + length] = bit;
}
if (length == 0) {
return -1;
if (--chainLength == 0) {
break;
}
lookup = m_prevTable[lookup & WINDOW_MASK];
}
}
for (i = 1; i <= PADDING; i++) {
insert (node - i);
if (bestLength == MIN_MATCH && (cur - anchor) >= (7 + 128)) {
bestLength = 0;
}
insert (node);
do {
if (m_matchLen > length) {
m_matchLen = length;
if (bestLength >= MIN_MATCH && bestLength < maxMatch && (cur - anchor) != 6) {
const int next = cur + 1;
const int targetLength = bestLength + 1;
const int limit = cr::max (next - WINDOW_SIZE, NIL);
int chainLength = MAX_CHAIN;
int lookup = m_hashTable[hash32 (&in[next])];
while (lookup > limit) {
if (in[lookup + bestLength] == in[next + bestLength] && load32 (&in[lookup]) == load32 (&in[next])) {
int length = MIN_MATCH;
while (length < targetLength && in[lookup + length] == in[next + length]) {
length++;
}
if (m_matchLen <= THRESHOLD) {
m_matchLen = 1;
cb[0] |= mask;
cb[cbp++] = m_buffer[node];
if (length == targetLength) {
bestLength = 0;
break;
}
}
if (--chainLength == 0) {
break;
}
lookup = m_prevTable[lookup & WINDOW_MASK];
}
}
if (bestLength >= MIN_MATCH) {
const int length = bestLength - MIN_MATCH;
const int token = ((dist >> 12) & 16) + cr::min (length, 15);
if (anchor != cur) {
const int run = cur - anchor;
if (run >= 7) {
add (op, (7 << 5) + token);
encode (op, run - 7);
}
else {
cb[cbp++] = (uint8) (m_matchPos & 0xff);
cb[cbp++] = (uint8) (((m_matchPos >> 4) & 0xf0) | (m_matchLen - (THRESHOLD + 1)));
add (op, (run << 5) + token);
}
if ((mask <<= 1) == 0) {
for (i = 0; i < cbp; i++) {
fp.putch (cb[i]);
}
m_csize += cbp;
cb[0] = 0;
cbp = mask = 1;
}
last = m_matchLen;
for (i = 0; (i < last) && (bp < bufferSize); i++) {
bit = buffer[bp++];
erase (ptr);
m_buffer[ptr] = bit;
if (ptr < PADDING - 1) {
m_buffer[ptr + MAXBUF] = bit;
}
ptr = (ptr + 1) & (MAXBUF - 1);
node = (node + 1) & (MAXBUF - 1);
insert (node);
}
while (i++ < last) {
erase (ptr);
ptr = (ptr + 1) & (MAXBUF - 1);
node = (node + 1) & (MAXBUF - 1);
if (length--) {
insert (node);
}
}
} while (length > 0);
if (cbp > 1) {
for (i = 0; i < cbp; i++) {
fp.putch (cb[i]);
}
m_csize += cbp;
}
fp.close ();
return m_csize;
}
int decode_ (const char *fileName, int headerSize, uint8 *buffer, int bufferSize) {
int i, j, k, node;
unsigned int flags;
int bp = 0;
uint8 bit;
File fp (fileName, "rb");
if (!fp.isValid ()) {
return -1;
}
fp.seek (headerSize, SEEK_SET);
node = MAXBUF - PADDING;
for (i = 0; i < node; i++) {
m_buffer[i] = ' ';
}
flags = 0;
for (;;) {
if (((flags >>= 1) & 256) == 0) {
int read = fp.getch ();
if (read == EOF) {
break;
}
bit = static_cast <uint8> (read);
flags = bit | 0xff00;
}
if (flags & 1) {
int read = fp.getch ();
if (read == EOF) {
break;
}
bit = static_cast <uint8> (read);
buffer[bp++] = bit;
if (bp > bufferSize) {
return -1;
}
m_buffer[node++] = bit;
node &= (MAXBUF - 1);
copy (op, &in[anchor], run);
op += run;
}
else {
if ((i = fp.getch ()) == EOF) {
add (op, token);
}
if (length >= 15) {
encode (op, length - 15);
}
store16 (op, dist);
op += 2;
while (bestLength-- != 0) {
const uint32 hash = hash32 (&in[cur]);
m_prevTable[cur & WINDOW_MASK] = m_hashTable[hash];
m_hashTable[hash] = cur++;
}
anchor = cur;
}
else {
const uint32 hash = hash32 (&in[cur]);
m_prevTable[cur & WINDOW_MASK] = m_hashTable[hash];
m_hashTable[hash] = cur++;
}
}
if (anchor != cur) {
const int run = cur - anchor;
if (run >= 7) {
add (op, 7 << 5);
encode (op, run - 7);
}
else {
add (op, run << 5);
}
copy (op, &in[anchor], run);
op += run;
}
return op - out;
}
int uncompress (uint8 *in, int inLength, uint8 *out, int outLength) {
uint8 *op = out;
uint8 *ip = in;
const uint8 *opEnd = op + outLength;
const uint8 *ipEnd = ip + inLength;
while (ip < ipEnd) {
const int token = *ip++;
if (token >= 32) {
int run = token >> 5;
if (run == 7) {
run += decode (ip);
}
if ((opEnd - op) < run || (ipEnd - ip) < run) {
return UNCOMPRESS_RESULT_FAILED;
}
copy (op, ip, run);
op += run;
ip += run;
if (ip >= ipEnd) {
break;
}
}
int length = (token & 15) + MIN_MATCH;
if ((j = fp.getch ()) == EOF) {
if (length == (15 + MIN_MATCH)) {
length += decode (ip);
}
if ((opEnd - op) < length) {
return UNCOMPRESS_RESULT_FAILED;
}
const int dist = ((token & 16) << 12) + load16 (ip);
ip += 2;
uint8 *cp = op - dist;
if ((op - out) < dist) {
return UNCOMPRESS_RESULT_FAILED;
}
if (dist >= 8) {
copy (op, cp, length);
op += length;
}
else
{
for (int i = 0; i < 4; i++) {
*op++ = *cp++;
}
while (length-- != 4) {
*op++ = *cp++;
}
}
}
return (ip == ipEnd) ? op - out : UNCOMPRESS_RESULT_FAILED;
}
private:
inline uint16 load16 (void *ptr) {
return *reinterpret_cast <const uint16 *> (ptr);
}
inline uint32 load32 (void *ptr) {
return *reinterpret_cast <const uint32 *> (ptr);
}
inline void store16 (void *ptr, int val) {
*reinterpret_cast <uint16 *> (ptr) = static_cast <uint16> (val);
}
inline void copy64 (void *dst, void *src) {
*reinterpret_cast <uint64 *> (dst) = *reinterpret_cast <const uint64 *> (src);
}
inline uint32 hash32 (void *ptr) {
return (load32 (ptr) * 0x9E3779B9) >> (32 - HASH_BITS);
}
inline void copy (uint8 *dst, uint8 *src, int count) {
copy64 (dst, src);
for (int i = 8; i < count; i += 8) {
copy64 (dst + i, src + i);
}
}
inline void add (uint8 *&dst, int val) {
*dst++ = static_cast <uint8> (val);
}
inline void encode (uint8 *&ptr, uint32 val) {
while (val >= 128) {
val -= 128;
*ptr++ = 128 + (val & 127);
val >>= 7;
}
*ptr++ = static_cast <uint8> (val);
}
inline uint32 decode (uint8 *&ptr) {
uint32 val = 0;
for (int i = 0; i <= 21; i += 7) {
const uint32 cur = *ptr++;
val += cur << i;
if (cur < 128) {
break;
}
i |= ((j & 0xf0) << 4);
j = (j & 0x0f) + THRESHOLD;
for (k = 0; k <= j; k++) {
bit = m_buffer[(i + k) & (MAXBUF - 1)];
buffer[bp++] = bit;
if (bp > bufferSize) {
return -1;
}
m_buffer[node++] = bit;
node &= (MAXBUF - 1);
}
}
}
fp.close ();
return bp;
}
// external decoder
static int decode (const char *fileName, int headerSize, uint8 *buffer, int bufferSize) {
static Compress compressor;
return compressor.decode_ (fileName, headerSize, buffer, bufferSize);
}
// external encoder
static int encode (const char *fileName, uint8 *header, int headerSize, uint8 *buffer, int bufferSize) {
static Compress compressor;
return compressor.encode_ (fileName, header, headerSize, buffer, bufferSize);
return val;
}
};

View file

@ -54,8 +54,9 @@ using int32 = signed long;
using uint8 = unsigned char;
using uint16 = unsigned short;
using uint32 = unsigned long;
using uint64 = unsigned long long;
}
};
using namespace cr::types;
@ -71,13 +72,14 @@ constexpr float D2R = PI / 180.0f;
constexpr float R2D = 180.0f / PI;
// from metamod-p
static inline bool checkptr (const void *ptr) {
static inline bool checkptr (void *ptr) {
#ifdef PLATFORM_WIN32
if (IsBadCodePtr (reinterpret_cast <FARPROC> (ptr)))
if (IsBadCodePtr (reinterpret_cast <FARPROC> (ptr))) {
return false;
#endif
}
#else
(void) (ptr);
#endif
return true;
}
@ -109,6 +111,10 @@ template <typename T> constexpr T abs (const T a) {
return a > 0 ? a : -a;
}
template <typename T> constexpr T bit (const T a) {
return 1 << a;
}
static inline float powf (const float x, const float y) {
union {
float d;
@ -124,7 +130,7 @@ static inline float sqrtf (const float value) {
}
static inline float sinf (const float value) {
const signed long sign = static_cast <signed long> (value * PI_RECIPROCAL);
const auto sign = static_cast <signed long> (value * PI_RECIPROCAL);
const float calc = (value - static_cast <float> (sign) * PI);
const float sqr = square (calc);
@ -135,7 +141,7 @@ static inline float sinf (const float value) {
}
static inline float cosf (const float value) {
const signed long sign = static_cast <signed long> (value * PI_RECIPROCAL);
const auto sign = static_cast <signed long> (value * PI_RECIPROCAL);
const float calc = (value - static_cast <float> (sign) * PI);
const float sqr = square (calc);
@ -303,7 +309,7 @@ protected:
NonCopyable (void) = default;
~NonCopyable (void) = default;
private:
public:
NonCopyable (const NonCopyable &) = delete;
NonCopyable &operator = (const NonCopyable &) = delete;
};
@ -323,33 +329,33 @@ public:
// see: https://github.com/preshing/RandomSequence/
class RandomSequence : public Singleton <RandomSequence> {
private:
unsigned int m_index;
unsigned int m_intermediateOffset;
unsigned long long m_divider;
uint32 m_index;
uint32 m_intermediateOffset;
uint64 m_divider;
private:
unsigned int premute (unsigned int x) {
static constexpr unsigned int prime = 4294967291u;
uint32 premute (uint32 x) {
static constexpr uint32 prime = 4294967291u;
if (x >= prime) {
return x;
}
const unsigned int residue = (static_cast <unsigned long long> (x) * x) % prime;
const uint32 residue = (static_cast <unsigned long long> (x) * x) % prime;
return (x <= prime / 2) ? residue : prime - residue;
}
unsigned int random (void) {
uint32 random (void) {
return premute ((premute (m_index++) + m_intermediateOffset) ^ 0x5bf03635);
}
public:
RandomSequence (void) {
const unsigned int seedBase = static_cast <unsigned int> (time (nullptr));
const unsigned int seedOffset = seedBase + 1;
const auto seedBase = static_cast <uint32> (time (nullptr));
const auto seedOffset = seedBase + 1;
m_index = premute (premute (seedBase) + 0x682f0161);
m_intermediateOffset = premute (premute (seedOffset) + 0x46790905);
m_divider = (static_cast <unsigned long long> (1)) << 32;
m_divider = (static_cast <uint64> (1)) << 32;
}
template <typename U> inline U getInt (U low, U high) {
@ -367,7 +373,7 @@ public:
class SimpleColor final : private NonCopyable {
public:
int red, green, blue;
int red = 0, green = 0, blue = 0;
inline void reset (void) {
red = green = blue = 0;
@ -390,7 +396,7 @@ public:
inline Vector (float scaler = 0.0f) : x (scaler), y (scaler), z (scaler) {}
inline Vector (float inputX, float inputY, float inputZ) : x (inputX), y (inputY), z (inputZ) {}
inline Vector (float *other) : x (other[0]), y (other[1]), z (other[2]) {}
inline Vector (const Vector &right) : x (right.x), y (right.y), z (right.z) {}
inline Vector (const Vector &right) = default;
public:
inline operator float * (void) {
@ -401,33 +407,33 @@ public:
return &x;
}
inline const Vector operator + (const Vector &right) const {
inline Vector operator + (const Vector &right) const {
return Vector (x + right.x, y + right.y, z + right.z);
}
inline const Vector operator - (const Vector &right) const {
inline Vector operator - (const Vector &right) const {
return Vector (x - right.x, y - right.y, z - right.z);
}
inline const Vector operator - (void) const {
inline Vector operator - (void) const {
return Vector (-x, -y, -z);
}
friend inline const Vector operator* (const float vec, const Vector &right) {
friend inline Vector operator * (const float vec, const Vector &right) {
return Vector (right.x * vec, right.y * vec, right.z * vec);
}
inline const Vector operator * (float vec) const {
inline Vector operator * (float vec) const {
return Vector (vec * x, vec * y, vec * z);
}
inline const Vector operator / (float vec) const {
inline Vector operator / (float vec) const {
const float inv = 1 / vec;
return Vector (inv * x, inv * y, inv * z);
}
// cross product
inline const Vector operator ^ (const Vector &right) const {
inline Vector operator ^ (const Vector &right) const {
return Vector (y * right.z - z * right.y, z * right.x - x * right.z, x * right.y - y * right.x);
}
@ -478,13 +484,7 @@ public:
return !fequal (x, right.x) && !fequal (y, right.y) && !fequal (z, right.z);
}
inline const Vector &operator = (const Vector &right) {
x = right.x;
y = right.y;
z = right.z;
return *this;
}
inline Vector &operator = (const Vector &right) = default;
public:
inline float length (void) const {
@ -572,7 +572,7 @@ public:
float cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f };
// compute the sine and cosine compontents
sincosf (deg2rad (x), deg2rad (y), deg2rad (y), sines, cosines);
sincosf (deg2rad (x), deg2rad (y), deg2rad (z), sines, cosines);
if (forward) {
forward->x = cosines[pitch] * cosines[yaw];
@ -596,12 +596,12 @@ public:
class Library final : private NonCopyable {
private:
void *m_ptr;
void *m_ptr = nullptr;
public:
explicit Library (void) : m_ptr (nullptr) { }
explicit Library (void) = default;
Library (const char *filename) : m_ptr (nullptr) {
Library (const char *filename) {
if (!filename) {
return;
}
@ -637,12 +637,13 @@ public:
if (!isValid ()) {
return nullptr;
}
return (R)
return reinterpret_cast <R> (
#ifdef PLATFORM_WIN32
GetProcAddress (static_cast <HMODULE> (m_ptr), function);
GetProcAddress (static_cast <HMODULE> (m_ptr), function)
#else
dlsym (m_ptr, function);
dlsym (m_ptr, function)
#endif
);
}
template <typename R> R handle (void) {
@ -656,11 +657,11 @@ public:
template <typename A, typename B> class Pair final {
public:
A first;
B second;
A first = move (A ());
B second = move (B ());
public:
Pair (const A &a, const B &b) : first (cr::move (a)), second (cr::move (b)) {
Pair (const A &a, const B &b) : first (move (a)), second (move (b)) {
}
public:
@ -673,13 +674,12 @@ public:
static constexpr size_t INVALID_INDEX = static_cast <size_t> (-1);
protected:
T *m_data;
size_t m_capacity;
size_t m_length;
T *m_data = nullptr;
size_t m_capacity = 0;
size_t m_length = 0;
public:
Array (void) : m_data (nullptr), m_capacity (0), m_length (0) {
}
Array (void) = default;
Array (Array &&other) noexcept {
m_data = other.m_data;
@ -689,7 +689,7 @@ public:
other.reset ();
}
virtual ~Array (void) {
~Array (void) {
destroy ();
}
@ -709,7 +709,7 @@ public:
if (m_length + growSize < m_capacity) {
return true;
}
size_t maxSize = max <size_t> (m_capacity + sizeof (T), static_cast <size_t> (16));
auto maxSize = max <size_t> (m_capacity + sizeof (T), static_cast <size_t> (16));
while (m_length + growSize > maxSize) {
maxSize *= 2;
@ -738,16 +738,16 @@ public:
bool res = reserve (newSize);
while (m_length < newSize) {
push (T ());
push (move (T ()));
}
return res;
}
inline size_t length (void) const {
size_t length (void) const {
return m_length;
}
inline size_t capacity (void) const {
size_t capacity (void) const {
return m_capacity;
}
@ -765,7 +765,11 @@ public:
return true;
}
inline T &at (size_t index) {
T &at (size_t index) {
return m_data[index];
}
const T &at (size_t index) const {
return m_data[index];
}
@ -861,7 +865,7 @@ public:
m_length = 0;
}
inline bool empty (void) const {
bool empty (void) const {
return m_length == 0;
}
@ -922,6 +926,12 @@ public:
return true;
}
void shuffle (void) {
for (size_t i = m_length; i >= 1; i--) {
swap (m_data[i - 1], m_data[RandomSequence::ref ().getInt (i, m_length - 2)]);
}
}
void reverse (void) {
for (size_t i = 0; i < m_length / 2; i++) {
swap (m_data[i], m_data[m_length - 1 - i]);
@ -1010,15 +1020,15 @@ public:
return value.first;
}
inline bool empty (void) const {
bool empty (void) const {
return !length ();
}
inline size_t length (void) const {
size_t length (void) const {
return m_length;
}
inline void clear (void) {
void clear (void) {
base::clear ();
}
@ -1081,7 +1091,12 @@ private:
}
};
// some fast string class
class String final : private Array <char> {
public:
static constexpr size_t INVALID_INDEX = Array <char>::INVALID_INDEX;
private:
using base = Array <char>;
@ -1097,9 +1112,9 @@ private:
}
public:
String (String &&other) noexcept : base (move (other)) { }
String (void) = default;
String (String &&other) noexcept : base (move (other)) { }
~String (void) = default;
public:
@ -1119,12 +1134,12 @@ public:
String &assign (const char *str, size_t length = 0) {
length = length > 0 ? length : strlen (str);
clear ();
resize (length);
base::clear ();
base::reserve (length + 1);
memcpy (m_data, str, length);
if (base::push (const_cast <char *> (str), length)) {
terminate ();
}
return *this;
}
@ -1133,7 +1148,7 @@ public:
}
String &assign (const char chr) {
resize (2);
resize (1);
m_data[0] = chr;
return *this;
@ -1143,11 +1158,9 @@ public:
if (empty ()) {
return assign (str);
}
const size_t maxLength = strlen (str) + 1;
resize (length () + maxLength);
strncat (m_data, str, maxLength);
if (push (const_cast <char *> (str), strlen (str))) {
terminate ();
};
return *this;
}
@ -1168,6 +1181,10 @@ public:
return base::length ();
}
size_t capacity (void) const {
return base::capacity ();
}
bool empty (void) const {
return !length ();
}
@ -1198,11 +1215,19 @@ public:
return atoi (chars ());
}
inline void terminate (void) {
float toFloat (void) const {
return static_cast <float> (atof (chars ()));
}
void terminate (void) {
m_data[m_length] = '\0';
}
inline char &at (size_t index) {
char &at (size_t index) {
return m_data[index];
}
const char &at (size_t index) const {
return m_data[index];
}
@ -1211,33 +1236,30 @@ public:
}
int32 compare (const char *what) const {
return strcmp (begin (), what);
return strcmp (m_data, what);
}
bool contains (const String &what) const {
return strstr (m_data, what.begin ()) != nullptr;
}
template <size_t BufferSize = 512> void format (const char *fmt, ...) {
va_list ap;
char buffer[BufferSize];
template <typename ...Args> void assign (const char *fmt, Args ...args) {
const size_t size = snprintf (nullptr, 0, fmt, args...);
va_start (ap, fmt);
vsnprintf (buffer, bufsize (buffer), fmt, ap);
va_end (ap);
reserve (size + 1);
resize (size);
assign (buffer);
snprintf (&m_data[0], size + 1, fmt, args...);
}
template <size_t BufferSize = 512> void formatAppend (const char *fmt, ...) {
va_list ap;
char buffer[BufferSize];
template <typename ...Args> void append (const char *fmt, Args ...args) {
const size_t size = snprintf (nullptr, 0, fmt, args...) + m_length;
const size_t len = m_length;
va_start (ap, fmt);
vsnprintf (buffer, bufsize (buffer), fmt, ap);
va_end (ap);
reserve (size + 1);
resize (size);
append (buffer);
snprintf (&m_data[len], size + 1, fmt, args...);
}
size_t insert (size_t at, const String &str) {
@ -1248,14 +1270,32 @@ public:
return 0;
}
size_t find (const String &search, size_t pos) const {
if (pos > length ()) {
size_t find (char val, size_t pos) const {
for (size_t i = pos; i < m_length; i++) {
if (m_data[i] == val) {
return i;
}
}
return INVALID_INDEX;
}
size_t find (const String &search, size_t pos) const {
size_t len = search.length ();
for (size_t i = pos; len + i <= length (); i++) {
if (strncmp (m_data + i, search.chars (), len) == 0) {
if (len > m_length || pos > m_length) {
return INVALID_INDEX;
}
for (size_t i = pos; i <= m_length - len; i++) {
size_t at = 0;
for (; search.m_data[at]; at++) {
if (m_data[i + at] != search.m_data[at]) {
break;
}
}
if (!search.m_data[at]) {
return i;
}
}
@ -1268,7 +1308,7 @@ public:
}
size_t numReplaced = 0, posIndex = 0;
while (posIndex < length ()) {
while (posIndex < m_length) {
posIndex = find (what, posIndex);
if (posIndex == INVALID_INDEX) {
@ -1283,28 +1323,96 @@ public:
return numReplaced;
}
String substr (size_t start, size_t count = INVALID_INDEX) {
String substr (size_t start, size_t count = INVALID_INDEX) const {
String result;
if (start >= length () || empty ()) {
return result;
if (start >= m_length || empty ()) {
return move (result);
}
if (count == INVALID_INDEX) {
count = length () - start;
count = m_length - start;
}
else if (start + count >= length ()) {
count = length () - start;
else if (start + count >= m_length) {
count = m_length - start;
}
result.resize (count);
transfer (&result[0], &m_data[start], count);
// copy, not move
for (size_t i = 0; i < count; i++) {
result[i] = m_data[start + i];
}
result[count] = '\0';
return result;
return move (result);
}
char &operator[] (size_t index) {
return at (index);
String &rtrim (const char *chars = "\r\n\t ") {
if (empty ()) {
return *this;
}
char *str = end () - 1;
while (*str != 0) {
if (isTrimChar (*str, chars)) {
erase (str - begin ());
str--;
}
else {
break;
}
}
return *this;
}
String &ltrim (const char *chars = "\r\n\t ") {
if (empty ()) {
return *this;
}
char *str = begin ();
while (isTrimChar (*str, chars)) {
str++;
}
if (begin () != str) {
erase (0, str - begin ());
}
return *this;
}
String &trim (const char *chars = "\r\n\t ") {
return rtrim (chars).ltrim (chars);
}
Array <String> split (const String &delim) const {
Array <String> tokens;
size_t prev = 0, pos = 0;
do {
pos = find (delim, prev);
if (pos == INVALID_INDEX) {
pos = m_length;
}
tokens.push (move (substr (prev, pos - prev)));
prev = pos + delim.length ();
} while (pos < m_length && prev < m_length);
return move (tokens);
}
String &lowercase (void) {
for (auto &ch : *this) {
ch = static_cast <char> (tolower (ch));
}
return *this;
}
String &uppercase (void) {
for (auto &ch : *this) {
ch = static_cast <char> (toupper (ch));
}
return *this;
}
friend String operator + (const String &lhs, const String &rhs) {
@ -1384,79 +1492,51 @@ public:
return append (rhs);
}
String &trimRight (const char *chars = "\r\n\t ") {
if (empty ()) {
return *this;
}
char *str = end () - 1;
while (*str != 0) {
if (isTrimChar (*str, chars)) {
erase (str - begin ());
str--;
}
else {
break;
}
}
return *this;
char &operator [] (size_t index) {
return at (index);
}
String &trimLeft (const char *chars = "\r\n\t ") {
if (empty ()) {
return *this;
}
char *str = begin ();
while (isTrimChar (*str, chars)) {
str++;
}
if (begin () != str) {
erase (0, str - begin ());
}
return *this;
}
String &trim (const char *chars = "\r\n\t ") {
return trimLeft (chars).trimRight (chars);
}
Array <String> split (const char *delimiter) {
Array <String> tokens;
size_t len, index = 0;
do {
index += strspn (&m_data[index], delimiter);
len = strcspn (&m_data[index], delimiter);
if (len > 0) {
tokens.push (move (substr (index, len)));
}
index += len;
} while (len > 0);
return tokens;
const char &operator [] (size_t index) const {
return at (index);
}
public:
static void trimChars (char *str) {
size_t pos = 0;
char *dest = str;
while (str[pos] <= ' ' && str[pos] > 0) {
pos++;
char *begin (void) {
return base::begin ();
}
while (str[pos]) {
*(dest++) = str[pos];
pos++;
char *begin (void) const {
return base::begin ();
}
*(dest--) = '\0';
while (dest >= str && *dest <= ' ' && *dest > 0) {
*(dest--) = '\0';
char *end (void) {
return base::end ();
}
char *end (void) const {
return base::end ();
}
public:
static String join (const Array <String> &sequence, const String &delim, size_t start = 0) {
if (sequence.empty ()) {
return "";
}
if (sequence.length () == 1) {
return sequence.at (0);
}
String result;
for (size_t index = start; index < sequence.length (); index++) {
if (index != start) {
result += delim + sequence[index];
}
else {
result += sequence[index];
}
}
return move (result);
}
};
@ -1553,17 +1633,16 @@ public:
class File : private NonCopyable {
private:
FILE *m_handle;
size_t m_size;
FILE *m_handle = nullptr;
size_t m_size = 0;
public:
File (void) : m_handle (nullptr), m_size (0) {}
File (const String &fileName, const String &mode = "rt") : m_handle (nullptr), m_size (0){
File (void) = default;
File (const String &fileName, const String &mode = "rt") {
open (fileName, mode);
}
virtual ~File (void) {
~File (void) {
close ();
}
@ -1594,14 +1673,24 @@ public:
return fflush (m_handle) ? false : true;
}
int getch (void) {
return fgetc (m_handle);
char getch (void) {
return static_cast <char> (fgetc (m_handle));
}
char *gets (char *buffer, int count) {
return fgets (buffer, count, m_handle);
}
bool getLine (String &line) {
line.clear ();
char ch;
while ((ch = getch ()) != '\n' && ch != EOF && !eof ()) {
line += ch;
}
return !eof ();
}
int writeFormat (const char *format, ...) {
assert (m_handle != nullptr);
@ -1714,14 +1803,13 @@ public:
class MemFile : private NonCopyable {
private:
size_t m_size;
size_t m_pos;
uint8 *m_buffer;
size_t m_size = 0;
size_t m_pos = 0;
uint8 *m_buffer = nullptr;
public:
MemFile (void) : m_size (0) , m_pos (0) , m_buffer (nullptr) {}
MemFile (const String &filename) : m_size (0) , m_pos (0) , m_buffer (nullptr) {
MemFile (void) = default;
MemFile (const String &filename) {
open (filename);
}
@ -1748,14 +1836,14 @@ public:
m_buffer = nullptr;
}
int getch (void) {
char getch (void) {
if (!m_buffer || m_pos >= m_size) {
return -1;
}
int readCh = m_buffer[m_pos];
m_pos++;
return readCh;
return static_cast <char> (readCh);
}
char *gets (char *buffer, size_t count) {
@ -1781,6 +1869,16 @@ public:
return index ? buffer : nullptr;
}
bool getLine (String &line) {
line.clear ();
char ch;
while ((ch = getch ()) != '\n' && ch != EOF && m_pos < m_size) {
line += ch;
}
return m_pos < m_size;
}
size_t read (void *buffer, size_t size, size_t count = 1) {
if (!m_buffer || m_size <= m_pos || !buffer || !size || !count) {
return 0;
@ -1818,7 +1916,7 @@ public:
return true;
}
inline size_t getSize (void) const {
size_t getSize (void) const {
return m_size;
}
@ -1827,8 +1925,7 @@ public:
}
};
}}
}};
namespace cr {
namespace types {
@ -1836,4 +1933,4 @@ namespace types {
using StringArray = cr::classes::Array <cr::classes::String>;
using IntArray = cr::classes::Array <int>;
}}
}};

View file

@ -10,22 +10,22 @@
#pragma once
// line draw
enum DrawLineType {
enum DrawLineType : int {
DRAW_SIMPLE,
DRAW_ARROW,
DRAW_NUM
};
// trace ignore
enum TraceIgnore {
enum TraceIgnore : int {
TRACE_IGNORE_NONE = 0,
TRACE_IGNORE_GLASS = (1 << 0),
TRACE_IGNORE_MONSTERS = (1 << 1),
TRACE_IGNORE_GLASS = cr::bit (0),
TRACE_IGNORE_MONSTERS = cr::bit (1),
TRACE_IGNORE_EVERYTHING = TRACE_IGNORE_GLASS | TRACE_IGNORE_MONSTERS
};
// variable type
enum VarType {
enum VarType : int {
VT_NORMAL = 0,
VT_READONLY,
VT_PASSWORD,
@ -34,7 +34,7 @@ enum VarType {
};
// netmessage functions
enum NetMsgId {
enum NetMsgId : int {
NETMSG_UNDEFINED = -1,
NETMSG_VGUI = 1,
NETMSG_SHOWMENU = 2,
@ -61,6 +61,36 @@ enum NetMsgId {
NETMSG_NUM = 25
};
// supported cs's
enum GameFlags : int {
GAME_CSTRIKE16 = cr::bit (0), // counter-strike 1.6 and above
GAME_XASH_ENGINE = cr::bit (1), // counter-strike 1.6 under the xash engine (additional flag)
GAME_CZERO = cr::bit (2), // counter-strike: condition zero
GAME_LEGACY = cr::bit (3), // counter-strike 1.3-1.5 with/without steam
GAME_MOBILITY = cr::bit (4), // additional flag that bot is running on android (additional flag)
GAME_OFFICIAL_CSBOT = cr::bit (5), // additional flag that indicates official cs bots are in game
GAME_METAMOD = cr::bit (6), // game running under meta\mod
GAME_CSDM = cr::bit (7), // csdm mod currently in use
GAME_CSDM_FFA = cr::bit (8), // csdm mod with ffa mode
GAME_REGAMEDLL = cr::bit (9), // server dll is a regamedll
GAME_SUPPORT_SVC_PINGS = cr::bit (10), // on that game version we can fake bots pings
GAME_SUPPORT_BOT_VOICE = cr::bit (11) // on that game version we can use chatter
};
// defines map type
enum MapFlags : int {
MAP_AS = cr::bit (0),
MAP_CS = cr::bit (1),
MAP_DE = cr::bit (2),
MAP_ES = cr::bit (3),
MAP_KA = cr::bit (4),
MAP_FY = cr::bit (5),
// additional flags
MAP_HAS_DOORS = cr::bit (6)
};
// variable reg pair
struct VarPair {
VarType type;
@ -79,6 +109,14 @@ struct MessageBlock {
int regMsgs[NETMSG_NUM];
};
// referentia vector info
struct RefVector {
Vector forward, right, up;
};
// entity prototype
using EntityFunction = void (*) (entvars_t *);
// compare language
struct LangComprarer {
size_t operator () (const String &key) const {
@ -96,15 +134,14 @@ struct LangComprarer {
};
// provides utility functions to not call original engine (less call-cost)
class Engine : public Singleton <Engine> {
class Game final : public Singleton <Game> {
private:
int m_drawModels[DRAW_NUM];
int m_spawnCount[TEAM_UNASSIGNED];
// bot client command
char m_arguments[256];
bool m_isBotCommand;
int m_argumentCount;
StringArray m_botArgs;
edict_t *m_startEntity;
edict_t *m_localEntity;
@ -112,19 +149,27 @@ private:
Array <VarPair> m_cvars;
HashMap <String, String, LangComprarer> m_language;
Library m_gameLib;
MessageBlock m_msgBlock;
bool m_precached;
int m_gameFlags;
int m_mapFlags;
float m_slowFrame; // per second updated frame
public:
Engine (void);
~Engine (void);
RefVector vec;
public:
Game (void);
~Game (void);
public:
// precaches internal stuff
void precache (void);
// initialize levels
void levelInitialize (void);
void levelInitialize (edict_t *ents, int max);
// prints data to servers console
void print (const char *fmt, ...);
@ -135,6 +180,9 @@ public:
// prints center message to all players
void centerPrint (const char *fmt, ...);
// prints center message to specified player
void centerPrint (edict_t *ent, const char *fmt, ...);
// prints message to client console
void clientPrint (edict_t *ent, const char *fmt, ...);
@ -189,122 +237,170 @@ public:
// checks whether softwared rendering is enabled
bool isSoftwareRenderer (void);
// load the cs binary in non metamod mode
bool loadCSBinary (void);
// do post-load stuff
bool postload (void);
// detects if csdm mod is in use
void detectDeathmatch (void);
// executes stuff every 1 second
void slowFrame (void);
// begin message handler
void beginMessage (edict_t *ent, int dest, int type);
// public inlines
public:
// get the current time on server
inline float timebase (void) {
return g_pGlobals->time;
float timebase (void) const {
return globals->time;
}
// get "maxplayers" limit on server
inline int maxClients (void) {
return g_pGlobals->maxClients;
int maxClients (void) const {
return globals->maxClients;
}
// get the fakeclient command interface
inline bool isBotCmd (void) {
bool isBotCmd (void) const {
return m_isBotCommand;
}
// gets custom engine args for client command
inline const char *botArgs (void) {
if (strncmp ("say ", m_arguments, 4) == 0) {
return &m_arguments[4];
}
else if (strncmp ("say_team ", m_arguments, 9) == 0) {
return &m_arguments[9];
}
return m_arguments;
const char *botArgs (void) const {
static String args;
args = String::join (m_botArgs, " ", m_botArgs[0] == "say" || m_botArgs[0] == "say_team" ? 1 : 0);
return args.chars ();
}
// gets custom engine argv for client command
inline const char *botArgv (int num) {
return getField (m_arguments, static_cast <size_t> (num));
const char *botArgv (size_t index) const {
if (index >= m_botArgs.length ()) {
return "";
}
return m_botArgs[index].chars ();
}
// gets custom engine argc for client command
inline int botArgc (void) {
return m_argumentCount;
int botArgc (void) const {
return m_botArgs.length ();
}
// gets edict pointer out of entity index
inline edict_t *entityOfIndex (const int index) {
edict_t *entityOfIndex (const int index) {
return static_cast <edict_t *> (m_startEntity + index);
};
// gets edict index out of it's pointer
inline int indexOfEntity (const edict_t *ent) {
int indexOfEntity (const edict_t *ent) {
return static_cast <int> (ent - m_startEntity);
};
// verify entity isn't null
inline bool isNullEntity (const edict_t *ent) {
bool isNullEntity (const edict_t *ent) {
return !ent || !indexOfEntity (ent) || ent->free;
}
// get the wroldspawn entity
inline edict_t *getStartEntity (void) {
edict_t *getStartEntity (void) {
return m_startEntity;
}
// get spawn count for team
inline int getSpawnCount (int team) {
int getSpawnCount (int team) const {
return m_spawnCount[team];
}
// gets the player team
inline int getTeam (edict_t *ent) {
extern Client g_clients[MAX_ENGINE_PLAYERS];
return g_clients[indexOfEntity (ent) - 1].team;
}
int getTeam (edict_t *ent);
// adds translation pair from config
inline void addTranslation (const String &original, const String &translated) {
void addTranslation (const String &original, const String &translated) {
m_language.put (original, translated);
}
// resets the message capture mechanism
inline void resetMessages (void) {
void resetMessages (void) {
m_msgBlock.msg = NETMSG_UNDEFINED;
m_msgBlock.state = 0;
m_msgBlock.bot = 0;
};
// sets the currently executed message
inline void setCurrentMessageId (int message) {
void setCurrentMessageId (int message) {
m_msgBlock.msg = message;
}
// set the bot entity that receive this message
inline void setCurrentMessageOwner (int id) {
void setCurrentMessageOwner (int id) {
m_msgBlock.bot = id;
}
// find registered message id
inline int getMessageId (int type) {
int getMessageId (int type) {
return m_msgBlock.regMsgs[type];
}
// assigns message id for message type
inline void setMessageId (int type, int id) {
void setMessageId (int type, int id) {
m_msgBlock.regMsgs[type] = id;
}
// tries to set needed message id
inline void captureMessage (int type, int msgId) {
void captureMessage (int type, int msgId) {
if (type == m_msgBlock.regMsgs[msgId]) {
setCurrentMessageId (msgId);
}
}
// sets the precache to uninitialize
inline void setUnprecached (void) {
void setUnprecached (void) {
m_precached = false;
}
// static utility functions
private:
const char *getField (const char *string, size_t id);
// gets the local entity (host edict)
edict_t *getLocalEntity (void) {
return m_localEntity;
}
// sets the local entity (host edict)
void setLocalEntity (edict_t *ent) {
m_localEntity = ent;
}
// builds referential vector
void makeVectors (const Vector &in) {
in.makeVectors (&vec.forward, &vec.right, &vec.up);
}
// what kind of map we're running ?
bool isMap (const int map) const {
return (m_mapFlags & map) == map;
}
// what kind of game engine / game dll / mod / tool we're running ?
bool is (const int type) const {
return (m_gameFlags & type) == type;
}
// adds game flag
void addGameFlag (const int type) {
m_gameFlags |= type;
}
// gets the map type
bool mapIs (const int type) const {
return (m_mapFlags & type) == type;
}
// get loaded gamelib
Library &getLib (void) {
return m_gameLib;
}
};
// simplify access for console variables
@ -314,35 +410,35 @@ public:
public:
ConVar (const char *name, const char *initval, VarType type = VT_NOSERVER, bool regMissing = false, const char *regVal = nullptr) : m_eptr (nullptr) {
Engine::ref ().pushVarToRegStack (name, initval, type, regMissing, regVal, this);
Game::ref ().pushVarToRegStack (name, initval, type, regMissing, regVal, this);
}
inline bool boolean (void) const {
bool boolean (void) const {
return m_eptr->value > 0.0f;
}
inline int integer (void) const {
int integer (void) const {
return static_cast <int> (m_eptr->value);
}
inline float flt (void) const {
float flt (void) const {
return m_eptr->value;
}
inline const char *str (void) const {
const char *str (void) const {
return m_eptr->string;
}
inline void set (float val) const {
g_engfuncs.pfnCVarSetFloat (m_eptr->name, val);
void set (float val) {
engfuncs.pfnCVarSetFloat (m_eptr->name, val);
}
inline void set (int val) const {
void set (int val) {
set (static_cast <float> (val));
}
inline void set (const char *val) const {
g_engfuncs.pfnCvar_DirectSet (m_eptr, const_cast <char *> (val));
void set (const char *val) {
engfuncs.pfnCvar_DirectSet (m_eptr, const_cast <char *> (val));
}
};
@ -366,36 +462,36 @@ public:
public:
MessageWriter &start (int dest, int type, const Vector &pos = Vector::null (), edict_t *to = nullptr) {
g_engfuncs.pfnMessageBegin (dest, type, pos, to);
engfuncs.pfnMessageBegin (dest, type, pos, to);
return *this;
}
void end (void) {
g_engfuncs.pfnMessageEnd ();
engfuncs.pfnMessageEnd ();
}
MessageWriter &writeByte (int val) {
g_engfuncs.pfnWriteByte (val);
engfuncs.pfnWriteByte (val);
return *this;
}
MessageWriter &writeChar (int val) {
g_engfuncs.pfnWriteChar (val);
engfuncs.pfnWriteChar (val);
return *this;
}
MessageWriter &writeShort (int val) {
g_engfuncs.pfnWriteShort (val);
engfuncs.pfnWriteShort (val);
return *this;
}
MessageWriter &writeCoord (float val) {
g_engfuncs.pfnWriteCoord (val);
engfuncs.pfnWriteCoord (val);
return *this;
}
MessageWriter &writeString (const char *val) {
g_engfuncs.pfnWriteString (val);
engfuncs.pfnWriteString (val);
return *this;
}
@ -409,26 +505,25 @@ public:
}
};
class LightMeasure final : public Singleton <LightMeasure> {
private:
lightstyle_t m_lightstyle[MAX_LIGHTSTYLES];
int m_lightstyleValue[MAX_LIGHTSTYLEVALUE];
bool m_doAnimation;
bool m_doAnimation = false;
SimpleColor m_point;
model_t *m_worldModel;
model_t *m_worldModel = nullptr;
public:
LightMeasure (void) : m_doAnimation (false), m_worldModel (nullptr) {
LightMeasure (void) {
initializeLightstyles ();
m_point.reset ();
}
public:
void initializeLightstyles (void);
void animateLight (void);
void updateLight (int style, char *value);
float getLightLevel (const Vector &point);
float getSkyColor (void);
@ -437,18 +532,18 @@ private:
template <typename S, typename M> bool recursiveLightPoint (const M *node, const Vector &start, const Vector &end);
public:
inline void resetWorldModel (void) {
void resetWorldModel (void) {
m_worldModel = nullptr;
}
inline void setWorldModel (model_t *model) {
void setWorldModel (model_t *model) {
if (m_worldModel) {
return;
}
m_worldModel = model;
}
inline void enableAnimation (bool enable) {
void enableAnimation (bool enable) {
m_doAnimation = enable;
}
};

View file

@ -112,7 +112,6 @@ typedef struct {
extern gamedll_funcs_t *gpGamedllFuncs;
extern mutil_funcs_t *gpMetaUtilFuncs;
extern meta_globals_t *gpMetaGlobals;
extern metamod_funcs_t gMetaFunctionTable;
#define MDLL_FUNC gpGamedllFuncs->dllapi_table

View file

@ -16,23 +16,24 @@
#ifndef SDKUTIL_H
#define SDKUTIL_H
extern globalvars_t *g_pGlobals;
extern enginefuncs_t g_engfuncs;
extern globalvars_t *globals;
extern enginefuncs_t engfuncs;
extern gamefuncs_t dllapi;
// Use this instead of ALLOC_STRING on constant strings
#define STRING(offset) (const char *)(g_pGlobals->pStringBase + (int)offset)
#define STRING(offset) (const char *)(globals->pStringBase + (int)offset)
// form fwgs-hlsdk
static inline int MAKE_STRING (const char *val) {
long long ptrdiff = val - STRING (0);
if (ptrdiff > INT_MAX || ptrdiff < INT_MIN) {
return g_engfuncs.pfnAllocString (val);
return engfuncs.pfnAllocString (val);
}
return static_cast <int> (ptrdiff);
}
#define ENGINE_STR(str) (const_cast <char *> (STRING (g_engfuncs.pfnAllocString (str))))
#define ENGINE_STR(str) (const_cast <char *> (STRING (engfuncs.pfnAllocString (str))))
// Dot products for view cone checking
#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees

View file

@ -1,73 +0,0 @@
//
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright (c) YaPB Development Team.
//
// This software is licensed under the BSD-style license.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://yapb.ru/license
//
#pragma once
extern bool g_canSayBombPlanted;
extern bool g_bombPlanted;
extern bool g_bombSayString;
extern bool g_roundEnded;
extern bool g_waypointOn;
extern bool g_autoWaypoint;
extern bool g_botsCanPause;
extern bool g_editNoclip;
extern bool g_gameWelcomeSent;
extern float g_autoPathDistance;
extern float g_timeBombPlanted;
extern float g_timeNextBombUpdate;
extern float g_lastChatTime;
extern float g_timeRoundEnd;
extern float g_timeRoundMid;
extern float g_timeRoundStart;
extern float g_timePerSecondUpdate;
extern float g_lastRadioTime[MAX_TEAM_COUNT];
extern int g_mapFlags;
extern int g_gameFlags;
extern int g_highestDamageCT;
extern int g_highestDamageT;
extern int g_highestKills;
extern int g_normalWeaponPrefs[NUM_WEAPONS];
extern int g_rusherWeaponPrefs[NUM_WEAPONS];
extern int g_carefulWeaponPrefs[NUM_WEAPONS];
extern int g_grenadeBuyPrecent[NUM_WEAPONS - 23];
extern int g_botBuyEconomyTable[NUM_WEAPONS - 15];
extern int g_radioSelect[MAX_ENGINE_PLAYERS];
extern int g_lastRadio[MAX_TEAM_COUNT];
extern int g_storeAddbotVars[4];
extern int *g_weaponPrefs[];
extern Array <StringArray> g_chatFactory;
extern Array <Array <ChatterItem>> g_chatterFactory;
extern Array <BotName> g_botNames;
extern Array <KeywordFactory> g_replyFactory;
extern WeaponSelect g_weaponSelect[NUM_WEAPONS + 1];
extern WeaponProperty g_weaponDefs[MAX_WEAPONS + 1];
extern Client g_clients[MAX_ENGINE_PLAYERS];
extern MenuText g_menus[BOT_MENU_TOTAL_MENUS];
extern Task g_taskFilters[TASK_MAX];
extern Experience *g_experienceData;
extern edict_t *g_hostEntity;
extern Library g_gameLib;
extern gamefuncs_t g_functionTable;
static inline bool isEmptyStr (const char *input) {
if (input == nullptr) {
return true;
}
return *input == '\0';
}

View file

@ -87,8 +87,11 @@
// @todo: sse should be working ok on x86 android?
#if defined(__ANDROID__)
#define PLATFORM_ANDROID
#if defined (__arm__) || defined (__aarch64__ )
#undef PLATFORM_HAS_SSE2
#endif
#endif
#else
#error "Platform unrecognized."
#endif

View file

@ -12,7 +12,7 @@
// general product information
#define PRODUCT_NAME "Yet Another POD-Bot"
#define PRODUCT_SHORT_NAME "YaPB"
#define PRODUCT_VERSION "2.10"
#define PRODUCT_VERSION "2.91"
#define PRODUCT_AUTHOR "YaPB Dev Team"
#define PRODUCT_URL "https://yapb.ru/"
#define PRODUCT_EMAIL "d@entix.io"
@ -26,7 +26,7 @@
#define PRODUCT_GIT_HASH "unspecified_hash"
#define PRODUCT_GIT_COMMIT_AUTHOR "unspecified_author"
#define PRODUCT_GIT_COMMIT_ID 0000
#define PRODUCT_VERSION_DWORD_INTERNAL 2, 10
#define PRODUCT_VERSION_DWORD_INTERNAL 2, 91
#define PRODUCT_VERSION_DWORD PRODUCT_VERSION_DWORD_INTERNAL, PRODUCT_GIT_COMMIT_ID
#define PRODUCT_SUPPORT_VERSION "Beta 6.6 - Condition Zero"
#define PRODUCT_COMMENTS "http://github.com/jeefo/yapb/"

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
//
// This software is licensed under the BSD-style license.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://yapb.jeefo.net/license
// https://yapb.ru/license
//
#include <winver.h>

View file

@ -22,7 +22,6 @@
<ClInclude Include="..\include\engine\meta_api.h" />
<ClInclude Include="..\include\engine\progdefs.h" />
<ClInclude Include="..\include\engine\util.h" />
<ClInclude Include="..\include\globals.h" />
<ClInclude Include="..\include\platform.h" />
<ClInclude Include="..\include\resource.h" />
</ItemGroup>
@ -32,7 +31,7 @@
<ClCompile Include="..\source\engine.cpp" />
<ClCompile Include="..\source\manager.cpp" />
<ClCompile Include="..\source\chatlib.cpp" />
<ClCompile Include="..\source\globals.cpp" />
<ClCompile Include="..\source\control.cpp" />
<ClCompile Include="..\source\interface.cpp" />
<ClCompile Include="..\source\navigate.cpp" />
<ClCompile Include="..\source\support.cpp" />
@ -193,7 +192,7 @@
<ProgramDataBaseFileName>.\release\inf\</ProgramDataBaseFileName>
<WarningLevel>Level4</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DebugInformationFormat>None</DebugInformationFormat>
<CompileAs>CompileAsCpp</CompileAs>
<InterproceduralOptimization>SingleFile</InterproceduralOptimization>
<FlushDenormalResultsToZero>true</FlushDenormalResultsToZero>
@ -209,6 +208,8 @@
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<WholeProgramOptimization>true</WholeProgramOptimization>
<StringPooling>true</StringPooling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FunctionLevelLinking>true</FunctionLevelLinking>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -228,8 +229,8 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<GenerateMapFile>false</GenerateMapFile>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>false</OptimizeReferences>
<EnableCOMDATFolding>false</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<SetChecksum>false</SetChecksum>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<DataExecutionPrevention />
@ -241,8 +242,9 @@
<IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
<ImageHasSafeExceptionHandlers>
</ImageHasSafeExceptionHandlers>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
<LinkTimeCodeGeneration>UseFastLinkTimeCodeGeneration</LinkTimeCodeGeneration>
<FullProgramDatabaseFile>true</FullProgramDatabaseFile>
<ShowProgress>NotSet</ShowProgress>
</Link>
<PostBuildEvent>
<Command>

View file

@ -21,9 +21,6 @@
<ClInclude Include="..\include\corelib.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="..\include\globals.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="..\include\resource.h">
<Filter>include</Filter>
</ClInclude>
@ -62,9 +59,6 @@
<ClCompile Include="..\source\chatlib.cpp">
<Filter>source</Filter>
</ClCompile>
<ClCompile Include="..\source\globals.cpp">
<Filter>source</Filter>
</ClCompile>
<ClCompile Include="..\source\interface.cpp">
<Filter>source</Filter>
</ClCompile>
@ -89,6 +83,9 @@
<ClCompile Include="..\source\basecode.cpp">
<Filter>source</Filter>
</ClCompile>
<ClCompile Include="..\source\control.cpp">
<Filter>source</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="yapb.rc">

View file

@ -20,7 +20,7 @@ LOCAL_SRC_FILES := \
manager.cpp \
chatlib.cpp \
combat.cpp \
globals.cpp \
control.cpp \
engine.cpp \
interface.cpp \
navigate.cpp \

File diff suppressed because it is too large Load diff

View file

@ -11,331 +11,80 @@
ConVar yb_chat ("yb_chat", "1");
void stripClanTags (char *buffer) {
// this function strips 'clan' tags specified below in given string buffer
if (isEmptyStr (buffer)) {
return;
}
size_t nameLength = strlen (buffer);
if (nameLength < 4) {
void BotUtils::stripTags (String &line) {
if (line.empty ()) {
return;
}
constexpr const char *open[] = { "-=", "-[", "-]", "-}", "-{", "<[", "<]", "[-", "]-", "{-", "}-", "[[", "[", "{", "]", "}", "<", ">", "-", "|", "=", "+", "(", ")" };
constexpr const char *close[] = { "=-", "]-", "[-", "{-", "}-", "]>", "[>", "-]", "-[", "-}", "-{", "]]", "]", "}", "[", "{", ">", "<", "-", "|", "=", "+", ")", "(" };
for (const auto &tag : m_tags) {
const size_t start = line.find (tag.first, 0);
// for each known tag...
for (size_t i = 0; i < cr::arrsize (open); i++) {
size_t start = strstr (buffer, open[i]) - buffer; // look for a tag start
if (start != String::INVALID_INDEX) {
const size_t end = line.find (tag.second, start);
const size_t diff = end - start;
// have we found a tag start?
if (start < 32) {
size_t stop = strstr (buffer, close[i]) - buffer; // look for a tag stop
// have we found a tag stop?
if (stop > start && stop < 32) {
size_t tag = strlen (close[i]);
size_t j = start;
for (; j < nameLength - (stop + tag - start); j++) {
buffer[j] = buffer[j + (stop + tag - start)]; // overwrite the buffer with the stripped string
if (end != String::INVALID_INDEX && end > start && diff < 32 && diff > 4) {
line.erase (start, diff + tag.second.length ());
break;
}
buffer[j] = '\0'; // terminate the string
}
}
}
// have we stripped too much (all the stuff)?
if (isEmptyStr (buffer)) {
String::trimChars (buffer);
void BotUtils::humanizePlayerName (String &playerName) {
if (playerName.empty ()) {
return;
}
String::trimChars (buffer); // if so, string is just a tag
// strip just the tag part...
for (size_t i = 0; i < cr::arrsize (open); i++) {
size_t start = strstr (buffer, open[i]) - buffer; // look for a tag start
// have we found a tag start?
if (start < 32) {
size_t tag = strlen (open[i]);
size_t j = start;
for (; j < nameLength - tag; j++) {
buffer[j] = buffer[j + tag]; // overwrite the buffer with the stripped string
}
buffer[j] = '\0'; // terminate the string
start = strstr (buffer, close[i]) - buffer; // look for a tag stop
// have we found a tag stop ?
if (start < 32) {
tag = strlen (close[i]);
j = start;
for (; j < nameLength - tag; j++) {
buffer[j] = buffer[j + tag]; // overwrite the buffer with the stripped string
}
buffer[j] = 0; // terminate the string
}
}
}
String::trimChars (buffer); // to finish, strip eventual blanks after and before the tag marks
}
char *humanizeName (char *name) {
// this function humanize player name (i.e. trim clan and switch to lower case (sometimes))
static char outputName[64]; // create return name buffer
strncpy (outputName, name, cr::bufsize (outputName)); // copy name to new buffer
// drop tag marks, 80 percent of time
if (rng.chance (80)) {
stripClanTags (outputName);
stripTags (playerName);
}
else {
String::trimChars (outputName);
playerName.trim ();
}
// sometimes switch name to lower characters
// note: since we're using russian names written in english, we reduce this shit to 6 percent
if (rng.chance (7)) {
for (size_t i = 0; i < strlen (outputName); i++) {
outputName[i] = static_cast <char> (tolower (static_cast <int> (outputName[i]))); // to lower case
// sometimes switch name to lower characters, only valid for the english languge
if (rng.chance (15) && strcmp (yb_language.str (), "en") == 0) {
playerName.lowercase ();
}
}
return &outputName[0]; // return terminated string
}
void addChatErrors (char *buffer) {
// this function humanize chat string to be more handwritten
size_t length = strlen (buffer); // get length of string
size_t i = 0;
// sometimes switch text to lowercase
// note: since we're using russian chat written in English, we reduce this shit to 4 percent
if (rng.chance (5)) {
for (i = 0; i < length; i++) {
buffer[i] = static_cast <char> (tolower (static_cast <int> (buffer[i]))); // switch to lowercase
}
void BotUtils::addChatErrors (String &line) {
// sometimes switch name to lower characters, only valid for the english languge
if (rng.chance (15) && strcmp (yb_language.str (), "en") == 0) {
line.lowercase ();
}
auto length = line.length ();
if (length > 15) {
size_t percentile = length / 2;
size_t percentile = line.length () / 2;
// "length / 2" percent of time drop a character
if (rng.getInt (1u, 100u) < percentile) {
size_t pos = rng.getInt (length / 8, length - length / 8); // chose random position in string
for (i = pos; i < length - 1; i++) {
buffer[i] = buffer[i + 1]; // overwrite the buffer with stripped string
}
buffer[i] = '\0'; // terminate string;
length--; // update new string length
if (rng.chance (percentile)) {
line.erase (rng.getInt (length / 8, length - length / 8));
}
// "length" / 4 precent of time swap character
if (rng.getInt (1u, 100u) < percentile / 2) {
if (rng.chance (percentile / 2)) {
size_t pos = rng.getInt (length / 8, 3 * length / 8); // choose random position in string
char ch = buffer[pos]; // swap characters
buffer[pos] = buffer[pos + 1];
buffer[pos + 1] = ch;
cr::swap (line[pos], line[pos + 1]);
}
}
buffer[length] = 0; // terminate string
}
void Bot::prepareChatMessage (char *text) {
// this function parses messages from the botchat, replaces keywords and converts names into a more human style
if (!yb_chat.boolean () || isEmptyStr (text)) {
return;
}
m_tempStrings.clear ();
char *textStart = text;
char *pattern = text;
edict_t *talkEntity = nullptr;
auto getHumanizedName = [] (edict_t *ent) {
if (!engine.isNullEntity (ent)) {
return const_cast <const char *> (humanizeName (const_cast <char *> (STRING (ent->v.netname))));
}
return "unknown";
};
while (pattern != nullptr) {
// all replacement placeholders start with a %
pattern = strchr (textStart, '%');
if (pattern != nullptr) {
size_t length = pattern - textStart;
if (length > 0) {
m_tempStrings = String (textStart, length);
}
pattern++;
// player with most frags?
if (*pattern == 'f') {
int highestFrags = -9000; // just pick some start value
int index = 0;
for (int i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
if (!(client.flags & CF_USED) || client.ent == ent ()) {
continue;
}
int frags = static_cast <int> (client.ent->v.frags);
if (frags > highestFrags) {
highestFrags = frags;
index = i;
}
}
talkEntity = g_clients[index].ent;
m_tempStrings += getHumanizedName (talkEntity);
}
// mapname?
else if (*pattern == 'm') {
m_tempStrings += engine.getMapName ();
}
// roundtime?
else if (*pattern == 'r') {
int time = static_cast <int> (g_timeRoundEnd - engine.timebase ());
m_tempStrings.formatAppend ("%02d:%02d", time / 60, time % 60);
}
// chat reply?
else if (*pattern == 's') {
talkEntity = engine.entityOfIndex (m_sayTextBuffer.entityIndex);
m_tempStrings += getHumanizedName (talkEntity);
}
// teammate alive?
else if (*pattern == 't') {
int i;
for (i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) {
continue;
}
break;
}
if (i < engine.maxClients ()) {
if (isPlayer (pev->dmg_inflictor) && m_team == engine.getTeam (pev->dmg_inflictor)) {
talkEntity = pev->dmg_inflictor;
}
else {
talkEntity = g_clients[i].ent;
}
m_tempStrings += getHumanizedName (talkEntity);
}
else // no teammates alive...
{
for (i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
if (!(client.flags & CF_USED) || client.team != m_team || client.ent == ent ()) {
continue;
}
break;
}
if (i < engine.maxClients ()) {
talkEntity = g_clients[i].ent;
m_tempStrings += getHumanizedName (talkEntity);
}
}
}
else if (*pattern == 'e') {
int i;
for (i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == ent ()) {
continue;
}
break;
}
if (i < engine.maxClients ()) {
talkEntity = g_clients[i].ent;
m_tempStrings += getHumanizedName (talkEntity);
}
// no teammates alive ?
else {
for (i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
if (!(client.flags & CF_USED) || client.team == m_team || client.ent == ent ()) {
continue;
}
break;
}
if (i < engine.maxClients ()) {
talkEntity = g_clients[i].ent;
m_tempStrings += getHumanizedName (talkEntity);
}
}
}
else if (*pattern == 'd') {
if (g_gameFlags & GAME_CZERO) {
if (rng.chance (30)) {
m_tempStrings += "CZ";
}
else {
m_tempStrings += "Condition Zero";
}
}
else if ((g_gameFlags & GAME_CSTRIKE16) || (g_gameFlags & GAME_LEGACY)) {
if (rng.chance (30)) {
m_tempStrings += "CS";
}
else {
m_tempStrings += "Counter-Strike";
}
}
}
else if (*pattern == 'v') {
talkEntity = m_lastVictim;
m_tempStrings += getHumanizedName (talkEntity);
}
pattern++;
textStart = pattern;
}
}
if (textStart != nullptr) {
// let the bots make some mistakes...
char tempString[160];
strncpy (tempString, textStart, cr::bufsize (tempString));
addChatErrors (tempString);
m_tempStrings += tempString;
}
}
bool checkForKeywords (char *message, char *reply) {
bool BotUtils::checkKeywords (const String &line, String &reply) {
// this function checks is string contain keyword, and generates reply to it
if (!yb_chat.boolean () || isEmptyStr (message)) {
if (!yb_chat.boolean () || line.empty ()) {
return false;
}
for (auto &factory : g_replyFactory) {
for (auto &factory : conf.getReplies ()) {
for (auto &keyword : factory.keywords) {
// check is keyword has occurred in message
if (strstr (message, keyword.chars ()) != nullptr) {
if (line.find (keyword, 0) != String::INVALID_INDEX) {
StringArray &usedReplies = factory.usedReplies;
if (usedReplies.length () >= factory.replies.length () / 2) {
@ -356,7 +105,7 @@ bool checkForKeywords (char *message, char *reply) {
// reply not used, so use it
if (!replyUsed) {
strcpy (reply, choosenReply.chars ()); // update final buffer
reply.assign (choosenReply); // update final buffer
usedReplies.push (choosenReply); // add to ignore list
return true;
@ -365,45 +114,234 @@ bool checkForKeywords (char *message, char *reply) {
}
}
}
auto &chat = conf.getChat ();
// didn't find a keyword? 70% of the time use some universal reply
if (rng.chance (70) && !g_chatFactory[CHAT_NOKW].empty ()) {
strcpy (reply, g_chatFactory[CHAT_NOKW].random ().chars ());
if (rng.chance (70) && !chat[CHAT_NOKW].empty ()) {
reply.assign (chat[CHAT_NOKW].random ());
return true;
}
return false;
}
bool Bot::processChatKeywords (char *reply) {
void Bot::prepareChatMessage (const String &message) {
// this function parses messages from the botchat, replaces keywords and converts names into a more human style
if (!yb_chat.boolean () || message.empty ()) {
return;
}
m_chatBuffer = message;
// must be called before return or on the end
auto finishPreparation = [&] (void) {
if (!m_chatBuffer.empty ()) {
util.addChatErrors (m_chatBuffer);
}
};
// need to check if we're have special symbols
size_t pos = message.find ('%', 0);
// nothing found, bail out
if (pos == String::INVALID_INDEX || pos >= message.length ()) {
finishPreparation ();
return;
}
// get the humanized name out of client
auto humanizedName = [] (const Client &client) -> String {
if (!util.isPlayer (client.ent)) {
return cr::move (String ("unknown"));
}
String playerName = STRING (client.ent->v.netname);
util.humanizePlayerName (playerName);
return cr::move (playerName);
};
// find highfrag player
auto getHighfragPlayer = [&] (void) -> String {
int highestFrags = -1;
int index = 0;
for (int i = 0; i < game.maxClients (); i++) {
const Client &client = util.getClient (i);
if (!(client.flags & CF_USED) || client.ent == ent ()) {
continue;
}
int frags = static_cast <int> (client.ent->v.frags);
if (frags > highestFrags) {
highestFrags = frags;
index = i;
}
}
return humanizedName (util.getClient (index));
};
// get roundtime
auto getRoundTime = [] (void) -> String {
int roundTimeSecs = static_cast <int> (bots.getRoundEndTime () - game.timebase ());
String roundTime;
roundTime.assign ("%02d:%02d", roundTimeSecs / 60, cr::abs (roundTimeSecs) % 60);
return cr::move (roundTime);
};
// get bot's victim
auto getMyVictim = [&] (void) -> String {
for (const Client &client : util.getClients ()) {
if (client.ent == m_lastVictim) {
return humanizedName (client);
}
}
return cr::move (String ("unknown"));
};
// get the game name alias
auto getGameName = [] (void) -> String {
String gameName;
if (game.is (GAME_CZERO)) {
if (rng.chance (30)) {
gameName = "CZ";
}
else {
gameName = "Condition Zero";
}
}
else if (game.is (GAME_CSTRIKE16) || game.is (GAME_LEGACY)) {
if (rng.chance (30)) {
gameName = "CS";
}
else {
gameName = "Counter-Strike";
}
}
return cr::move (gameName);
};
// get enemy or teammate alive
auto getPlayerAlive = [&] (bool needsEnemy) -> String {
int index;
for (index = 0; index < game.maxClients (); index++) {
const Client &client = util.getClient (index);
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == ent ()) {
continue;
}
if ((needsEnemy && m_team == client.team) || (!needsEnemy && m_team != client.team)) {
continue;
}
break;
}
if (index < game.maxClients ()) {
if (!needsEnemy && util.isPlayer (pev->dmg_inflictor) && m_team == game.getTeam (pev->dmg_inflictor)) {
return humanizedName (util.getClient (game.indexOfEntity (pev->dmg_inflictor) - 1));
}
else {
return humanizedName (util.getClient (index));
}
}
else {
for (index = 0; index < game.maxClients (); index++) {
const Client &client = util.getClient (index);
if (!(client.flags & CF_USED) || client.team != m_team || client.ent == ent ()) {
continue;
}
if ((needsEnemy && m_team != client.team) || (!needsEnemy && m_team == client.team)) {
continue;
}
break;
}
if (index < game.maxClients ()) {
return humanizedName (util.getClient (index));
}
}
return cr::move (String ("unknown"));
};
// found one, let's do replace
switch (message[pos + 1]) {
// the highest frag player
case 'f':
m_chatBuffer.replace ("%f", getHighfragPlayer ());
break;
// current map name
case 'm':
m_chatBuffer.replace ("%m", game.getMapName ());
break;
// round time
case 'r':
m_chatBuffer.replace ("%r", getRoundTime ());
break;
// chat reply
case 's':
if (m_sayTextBuffer.entityIndex != -1) {
m_chatBuffer.replace ("%s", humanizedName (util.getClient (m_sayTextBuffer.entityIndex)));
}
else {
m_chatBuffer.replace ("%s", getHighfragPlayer ());
}
break;
// last bot victim
case 'v':
m_chatBuffer.replace ("%v", getMyVictim ());
break;
// game name
case 'd':
m_chatBuffer.replace ("%d", getGameName ());
break;
// teammate alive
case 't':
m_chatBuffer.replace ("%t", getPlayerAlive (false));
break;
// enemy alive
case 'e':
m_chatBuffer.replace ("%e", getPlayerAlive (true));
break;
};
finishPreparation ();
}
bool Bot::checkChatKeywords (String &reply) {
// this function parse chat buffer, and prepare buffer to keyword searching
char tempMessage[512];
size_t maxLength = cr::bufsize (tempMessage);
strncpy (tempMessage, m_sayTextBuffer.sayText.chars (), maxLength); // copy to safe place
// text to uppercase for keyword parsing
for (size_t i = 0; i < maxLength; i++) {
tempMessage[i] = static_cast <char> (toupper (static_cast <int> (tempMessage[i])));
}
return checkForKeywords (tempMessage, reply);
String message = m_sayTextBuffer.sayText;
return util.checkKeywords (message.uppercase (), reply);
}
bool Bot::isReplyingToChat (void) {
// this function sends reply to a player
if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) {
char text[256];
String message;
// check is time to chat is good
if (m_sayTextBuffer.timeNextChat < engine.timebase ()) {
if (rng.chance (m_sayTextBuffer.chatProbability + rng.getInt (15, 35)) && processChatKeywords (reinterpret_cast <char *> (&text))) {
prepareChatMessage (text);
if (m_sayTextBuffer.timeNextChat < game.timebase ()) {
if (rng.chance (m_sayTextBuffer.chatProbability + rng.getInt (25, 45)) && checkChatKeywords (message)) {
prepareChatMessage (message);
pushMsgQueue (GAME_MSG_SAY_CMD);
m_sayTextBuffer.entityIndex = -1;
m_sayTextBuffer.timeNextChat = engine.timebase () + m_sayTextBuffer.chatDelay;
m_sayTextBuffer.timeNextChat = game.timebase () + m_sayTextBuffer.chatDelay;
m_sayTextBuffer.sayText.clear ();
return true;
@ -415,20 +353,61 @@ bool Bot::isReplyingToChat (void) {
return false;
}
void Bot::checkForChat (void) {
// bot chatting turned on?
if (!m_notKilled && yb_chat.boolean () && m_lastChatTime + 10.0 < game.timebase () && bots.getLastChatTimestamp () + 5.0f < game.timebase () && !isReplyingToChat ()) {
auto &chat = conf.getChat ();
// say a text every now and then
if (rng.chance (50)) {
m_lastChatTime = game.timebase ();
bots.setLastChatTimestamp (game.timebase ());
if (!chat[CHAT_DEAD].empty ()) {
const String &phrase = chat[CHAT_DEAD].random ();
bool sayBufferExists = false;
// search for last messages, sayed
for (auto &sentence : m_sayTextBuffer.lastUsedSentences) {
if (strncmp (sentence.chars (), phrase.chars (), sentence.length ()) == 0) {
sayBufferExists = true;
break;
}
}
if (!sayBufferExists) {
prepareChatMessage (phrase);
pushMsgQueue (GAME_MSG_SAY_CMD);
// add to ignore list
m_sayTextBuffer.lastUsedSentences.push (phrase);
}
}
// clear the used line buffer every now and then
if (static_cast <int> (m_sayTextBuffer.lastUsedSentences.length ()) > rng.getInt (4, 6)) {
m_sayTextBuffer.lastUsedSentences.clear ();
}
}
}
}
void Bot::say (const char *text) {
// this function prints saytext message to all players
if (isEmptyStr (text) || !yb_chat.boolean ()) {
if (util.isEmptyStr (text) || !yb_chat.boolean ()) {
return;
}
engine.execBotCmd (ent (), "say \"%s\"", text);
game.execBotCmd (ent (), "say \"%s\"", text);
}
void Bot::sayTeam (const char *text) {
// this function prints saytext message only for teammates
if (isEmptyStr (text) || !yb_chat.boolean ()) {
if (util.isEmptyStr (text) || !yb_chat.boolean ()) {
return;
}
engine.execBotCmd (ent (), "say_team \"%s\"", text);
game.execBotCmd (ent (), "say_team \"%s\"", text);
}

File diff suppressed because it is too large Load diff

2031
source/control.cpp Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,379 +0,0 @@
//
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright (c) YaPB Development Team.
//
// This software is licensed under the BSD-style license.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://yapb.ru/license
//
#include <yapb.h>
bool g_canSayBombPlanted = true;
bool g_roundEnded = true;
bool g_botsCanPause = false;
bool g_bombPlanted = false;
bool g_bombSayString = false;
bool g_gameWelcomeSent = false;
bool g_editNoclip = false;
bool g_waypointOn = false;
bool g_autoWaypoint = false;
float g_lastChatTime = 0.0f;
float g_timeRoundStart = 0.0f;
float g_timeRoundEnd = 0.0f;
float g_timeRoundMid = 0.0f;
float g_timeNextBombUpdate = 0.0f;
float g_timeBombPlanted = 0.0f;
float g_timePerSecondUpdate = 0.0f;
float g_lastRadioTime[MAX_TEAM_COUNT] = { 0.0f, };
float g_autoPathDistance = 250.0f;
int g_lastRadio[MAX_TEAM_COUNT];
int g_storeAddbotVars[4];
int g_radioSelect[MAX_ENGINE_PLAYERS];
int g_gameFlags = 0;
int g_mapFlags = 0;
int g_highestDamageCT = 1;
int g_highestDamageT = 1;
int g_highestKills = 1;
Array <StringArray> g_chatFactory;
Array <Array <ChatterItem>> g_chatterFactory;
Array <BotName> g_botNames;
Array <KeywordFactory> g_replyFactory;
Library g_gameLib;
meta_globals_t *gpMetaGlobals = nullptr;
gamedll_funcs_t *gpGamedllFuncs = nullptr;
mutil_funcs_t *gpMetaUtilFuncs = nullptr;
gamefuncs_t g_functionTable;
enginefuncs_t g_engfuncs;
Client g_clients[MAX_ENGINE_PLAYERS];
WeaponProperty g_weaponDefs[MAX_WEAPONS + 1];
edict_t *g_hostEntity = nullptr;
globalvars_t *g_pGlobals = nullptr;
Experience *g_experienceData = nullptr;
// default tables for personality weapon preferences, overridden by weapons.cfg
int g_normalWeaponPrefs[NUM_WEAPONS] = {0, 2, 1, 4, 5, 6, 3, 12, 10, 24, 25, 13, 11, 8, 7, 22, 23, 18, 21, 17, 19, 15, 17, 9, 14, 16};
int g_rusherWeaponPrefs[NUM_WEAPONS] = {0, 2, 1, 4, 5, 6, 3, 24, 19, 22, 23, 20, 21, 10, 12, 13, 7, 8, 11, 9, 18, 17, 19, 25, 15, 16};
int g_carefulWeaponPrefs[NUM_WEAPONS] = {0, 2, 1, 4, 25, 6, 3, 7, 8, 12, 10, 13, 11, 9, 24, 18, 14, 17, 16, 15, 19, 20, 21, 22, 23, 5};
int g_grenadeBuyPrecent[NUM_WEAPONS - 23] = {95, 85, 60};
int g_botBuyEconomyTable[NUM_WEAPONS - 15] = {1900, 2100, 2100, 4000, 6000, 7000, 16000, 1200, 800, 1000, 3000};
int *g_weaponPrefs[] = {g_normalWeaponPrefs, g_rusherWeaponPrefs, g_carefulWeaponPrefs};
// metamod plugin information
plugin_info_t Plugin_info = {
META_INTERFACE_VERSION, // interface version
PRODUCT_SHORT_NAME, // plugin name
PRODUCT_VERSION, // plugin version
PRODUCT_DATE, // date of creation
PRODUCT_AUTHOR, // plugin author
PRODUCT_URL, // plugin URL
PRODUCT_LOGTAG, // plugin logtag
PT_CHANGELEVEL, // when loadable
PT_ANYTIME, // when unloadable
};
// table with all available actions for the bots (filtered in & out in Bot::setConditions) some of them have subactions included
Task g_taskFilters[TASK_MAX] = {
{ TASK_NORMAL, 0, INVALID_WAYPOINT_INDEX, 0.0f, true },
{ TASK_PAUSE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_MOVETOPOSITION, 0, INVALID_WAYPOINT_INDEX, 0.0f, true },
{ TASK_FOLLOWUSER, 0, INVALID_WAYPOINT_INDEX, 0.0f, true },
{ TASK_PICKUPITEM, 0, INVALID_WAYPOINT_INDEX, 0.0f, true },
{ TASK_CAMP, 0, INVALID_WAYPOINT_INDEX, 0.0f, true },
{ TASK_PLANTBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_DEFUSEBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_ATTACK, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_HUNTENEMY, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_SEEKCOVER, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_THROWHEGRENADE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_THROWFLASHBANG, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_THROWSMOKE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_DOUBLEJUMP, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_ESCAPEFROMBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_SHOOTBREAKABLE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_HIDE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_BLINDED, 0, INVALID_WAYPOINT_INDEX, 0.0f, false },
{ TASK_SPRAY, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }
};
// weapons and their specifications
WeaponSelect g_weaponSelect[NUM_WEAPONS + 1] = {
{ WEAPON_KNIFE, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, true },
{ WEAPON_USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, false },
{ WEAPON_GLOCK, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, 0, false },
{ WEAPON_DEAGLE, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, 2, false },
{ WEAPON_P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, 0 , false },
{ WEAPON_ELITE, "weapon_elite", "elite.mdl", 800, 1, 0, 0, 1, 5, 5, 5, 0, false },
{ WEAPON_FIVESEVEN, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, 0, false },
{ WEAPON_M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, 0, false },
{ WEAPON_XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, 0, false },
{ WEAPON_MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, 0, true },
{ WEAPON_TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, 0, true },
{ WEAPON_P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, 0, true },
{ WEAPON_MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, 0, true },
{ WEAPON_UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, 0, true },
{ WEAPON_AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, 2, true },
{ WEAPON_SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, 2, true },
{ WEAPON_M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, 2, true },
{ WEAPON_GALIL, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, 2, true },
{ WEAPON_FAMAS, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, 2, true },
{ WEAPON_AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, 2, true },
{ WEAPON_SCOUT, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, 3, false },
{ WEAPON_AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, 3, false },
{ WEAPON_G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, 3, false },
{ WEAPON_SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, 3, false },
{ WEAPON_M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, 2, true },
{ WEAPON_SHIELD, "weapon_shield", "shield.mdl", 2200, 0, 1, 1, 8, -1, 8, 8, 0, false },
{ 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, false }
};
void setupBotMenus (void) {
int counter = 0;
auto buildKeys = [](int numKeys) {
int keys = 0;
for (int i = 0; i < numKeys; i++) {
keys |= (1 << i);
}
keys |= (1 << 9);
return keys;
};
// bots main menu
g_menus[counter] = {
BOT_MENU_MAIN, buildKeys (4),
"\\yMain Menu\\w\n\n"
"1. Control Bots\n"
"2. Features\n\n"
"3. Fill Server\n"
"4. End Round\n\n"
"0. Exit"
};
// bots features menu
g_menus[++counter] = {
BOT_MENU_FEATURES, buildKeys (5),
"\\yBots Features\\w\n\n"
"1. Weapon Mode Menu\n"
"2. Waypoint Menu\n"
"3. Select Personality\n\n"
"4. Toggle Debug Mode\n"
"5. Command Menu\n\n"
"0. Exit"
};
// bot control menu
g_menus[++counter] = {
BOT_MENU_CONTROL, buildKeys (5),
"\\yBots Control Menu\\w\n\n"
"1. Add a Bot, Quick\n"
"2. Add a Bot, Specified\n\n"
"3. Remove Random Bot\n"
"4. Remove All Bots\n\n"
"5. Remove Bot Menu\n\n"
"0. Exit"
};
// weapon mode select menu
g_menus[++counter] = {
BOT_MENU_WEAPON_MODE, buildKeys (7),
"\\yBots Weapon Mode\\w\n\n"
"1. Knives only\n"
"2. Pistols only\n"
"3. Shotguns only\n"
"4. Machine Guns only\n"
"5. Rifles only\n"
"6. Sniper Weapons only\n"
"7. All Weapons\n\n"
"0. Exit"
};
// personality select menu
g_menus[++counter] = {
BOT_MENU_PERSONALITY, buildKeys (4),
"\\yBots Personality\\w\n\n"
"1. Random\n"
"2. Normal\n"
"3. Aggressive\n"
"4. Careful\n\n"
"0. Exit"
};
// difficulty select menu
g_menus[++counter] = {
BOT_MENU_DIFFICULTY, buildKeys (5),
"\\yBots Difficulty Level\\w\n\n"
"1. Newbie\n"
"2. Average\n"
"3. Normal\n"
"4. Professional\n"
"5. Godlike\n\n"
"0. Exit"
};
// team select menu
g_menus[++counter] = {
BOT_MENU_TEAM_SELECT, buildKeys (5),
"\\ySelect a team\\w\n\n"
"1. Terrorist Force\n"
"2. Counter-Terrorist Force\n\n"
"5. Auto-select\n\n"
"0. Exit"
};
// terrorist model select menu
g_menus[++counter] = {
BOT_MENU_TERRORIST_SELECT, buildKeys (5),
"\\ySelect an appearance\\w\n\n"
"1. Phoenix Connexion\n"
"2. L337 Krew\n"
"3. Arctic Avengers\n"
"4. Guerilla Warfare\n\n"
"5. Auto-select\n\n"
"0. Exit"
};
// counter-terrorist model select menu
g_menus[++counter] = {
BOT_MENU_CT_SELECT, buildKeys (5),
"\\ySelect an appearance\\w\n\n"
"1. Seal Team 6 (DEVGRU)\n"
"2. German GSG-9\n"
"3. UK SAS\n"
"4. French GIGN\n\n"
"5. Auto-select\n\n"
"0. Exit"
};
// command menu
g_menus[++counter] = {
BOT_MENU_COMMANDS, buildKeys (4),
"\\yBot Command Menu\\w\n\n"
"1. Make Double Jump\n"
"2. Finish Double Jump\n\n"
"3. Drop the C4 Bomb\n"
"4. Drop the Weapon\n\n"
"0. Exit"
};
// main waypoint menu
g_menus[++counter] = {
BOT_MENU_WAYPOINT_MAIN_PAGE1, buildKeys (9),
"\\yWaypoint Operations (Page 1)\\w\n\n"
"1. Show/Hide waypoints\n"
"2. Cache waypoint\n"
"3. Create path\n"
"4. Delete path\n"
"5. Add waypoint\n"
"6. Delete waypoint\n"
"7. Set Autopath Distance\n"
"8. Set Radius\n\n"
"9. Next...\n\n"
"0. Exit"
};
// main waypoint menu (page 2)
g_menus[++counter] = {
BOT_MENU_WAYPOINT_MAIN_PAGE2, buildKeys (9),
"\\yWaypoint Operations (Page 2)\\w\n\n"
"1. Waypoint stats\n"
"2. Autowaypoint on/off\n"
"3. Set flags\n"
"4. Save waypoints\n"
"5. Save without checking\n"
"6. Load waypoints\n"
"7. Check waypoints\n"
"8. Noclip cheat on/off\n\n"
"9. Previous...\n\n"
"0. Exit"
};
// select waypoint radius menu
g_menus[++counter] = {
BOT_MENU_WAYPOINT_RADIUS, buildKeys (9),
"\\yWaypoint Radius\\w\n\n"
"1. SetRadius 0\n"
"2. SetRadius 8\n"
"3. SetRadius 16\n"
"4. SetRadius 32\n"
"5. SetRadius 48\n"
"6. SetRadius 64\n"
"7. SetRadius 80\n"
"8. SetRadius 96\n"
"9. SetRadius 128\n\n"
"0. Exit"
};
// waypoint add menu
g_menus[++counter] = {
BOT_MENU_WAYPOINT_TYPE, buildKeys (9),
"\\yWaypoint Type\\w\n\n"
"1. Normal\n"
"\\r2. Terrorist Important\n"
"3. Counter-Terrorist Important\n"
"\\w4. Block with hostage / Ladder\n"
"\\y5. Rescue Zone\n"
"\\w6. Camping\n"
"7. Camp End\n"
"\\r8. Map Goal\n"
"\\w9. Jump\n\n"
"0. Exit"
};
// set waypoint flag menu
g_menus[++counter] = {
BOT_MENU_WAYPOINT_FLAG, buildKeys (5),
"\\yToggle Waypoint Flags\\w\n\n"
"1. Block with Hostage\n"
"2. Terrorists Specific\n"
"3. CTs Specific\n"
"4. Use Elevator\n"
"5. Sniper Point (\\yFor Camp Points Only!\\w)\n\n"
"0. Exit"
};
// auto-path max distance
g_menus[++counter] = {
BOT_MENU_WAYPOINT_AUTOPATH, buildKeys (7),
"\\yAutoPath Distance\\w\n\n"
"1. Distance 0\n"
"2. Distance 100\n"
"3. Distance 130\n"
"4. Distance 160\n"
"5. Distance 190\n"
"6. Distance 220\n"
"7. Distance 250 (Default)\n\n"
"0. Exit"
};
// path connections
g_menus[++counter] = {
BOT_MENU_WAYPOINT_PATH, buildKeys (3),
"\\yCreate Path (Choose Direction)\\w\n\n"
"1. Outgoing Path\n"
"2. Incoming Path\n"
"3. Bidirectional (Both Ways)\n\n"
"0. Exit"
};
const String &empty = "";
// kick menus
g_menus[++counter] = { BOT_MENU_KICK_PAGE_1, 0x0, empty, };
g_menus[++counter] = { BOT_MENU_KICK_PAGE_2, 0x0, empty, };
g_menus[++counter] = { BOT_MENU_KICK_PAGE_3, 0x0, empty, };
g_menus[++counter] = { BOT_MENU_KICK_PAGE_4, 0x0, empty, };
}
// bot menus
MenuText g_menus[BOT_MENU_TOTAL_MENUS];

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,3 @@
//
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright (c) YaPB Development Team.
//
@ -9,13 +8,57 @@
#include <yapb.h>
ConVar yb_display_menu_text ("yb_display_menu_text", "1");
ConVar yb_display_welcome_text ("yb_display_welcome_text", "1");
ConVar mp_roundtime ("mp_roundtime", nullptr, VT_NOREGISTER);
ConVar mp_freezetime ("mp_freezetime", nullptr, VT_NOREGISTER, true, "0");
BotUtils::BotUtils (void) {
m_needToSendWelcome = false;
m_welcomeReceiveTime = 0.0f;
const char *format (const char *format, ...) {
// add default messages
m_sentences.push ("hello user,communication is acquired");
m_sentences.push ("your presence is acknowledged");
m_sentences.push ("high man, your in command now");
m_sentences.push ("blast your hostile for good");
m_sentences.push ("high man, kill some idiot here");
m_sentences.push ("is there a doctor in the area");
m_sentences.push ("warning, experimental materials detected");
m_sentences.push ("high amigo, shoot some but");
m_sentences.push ("attention, hours of work software, detected");
m_sentences.push ("time for some bad ass explosion");
m_sentences.push ("bad ass son of a breach device activated");
m_sentences.push ("high, do not question this great service");
m_sentences.push ("engine is operative, hello and goodbye");
m_sentences.push ("high amigo, your administration has been great last day");
m_sentences.push ("attention, expect experimental armed hostile presence");
m_sentences.push ("warning, medical attention required");
m_tags.push ({ "[[", "]]" });
m_tags.push ({ "-=", "=-" });
m_tags.push ({ "-[", "]-" });
m_tags.push ({ "-]", "[-" });
m_tags.push ({ "-}", "{-" });
m_tags.push ({ "-{", "}-" });
m_tags.push ({ "<[", "]>" });
m_tags.push ({ "<]", "[>" });
m_tags.push ({ "[-", "-]" });
m_tags.push ({ "]-", "-[" });
m_tags.push ({ "{-", "-}" });
m_tags.push ({ "}-", "-{" });
m_tags.push ({ "[", "]" });
m_tags.push ({ "{", "}" });
m_tags.push ({ "<", "[" });
m_tags.push ({ ">", "<" });
m_tags.push ({ "-", "-" });
m_tags.push ({ "|", "|" });
m_tags.push ({ "=", "=" });
m_tags.push ({ "+", "+" });
m_tags.push ({ "(", ")" });
m_tags.push ({ ")", "(" });
m_clients.resize (MAX_ENGINE_PLAYERS + 1);
}
const char *BotUtils::format (const char *format, ...) {
static char strBuffer[2][MAX_PRINT_BUFFER];
static int rotator = 0;
@ -32,30 +75,30 @@ const char *format (const char *format, ...) {
return ptr;
}
bool isAlive (edict_t *ent) {
if (engine.isNullEntity (ent)) {
bool BotUtils::isAlive (edict_t *ent) {
if (game.isNullEntity (ent)) {
return false;
}
return ent->v.deadflag == DEAD_NO && ent->v.health > 0 && ent->v.movetype != MOVETYPE_NOCLIP;
}
float getShootingConeDeviation (edict_t *ent, const Vector &position) {
makeVectors (ent->v.v_angle);
float BotUtils::getShootingCone (edict_t *ent, const Vector &position) {
game.makeVectors (ent->v.v_angle);
// he's facing it, he meant it
return g_pGlobals->v_forward | (position - (ent->v.origin + ent->v.view_ofs)).normalize ();
return game.vec.forward | (position - (ent->v.origin + ent->v.view_ofs)).normalize ();
}
bool isInViewCone (const Vector &origin, edict_t *ent) {
return getShootingConeDeviation (ent, origin) >= cr::cosf (cr::deg2rad ((ent->v.fov > 0 ? ent->v.fov : 90.0f) * 0.5f));
bool BotUtils::isInViewCone (const Vector &origin, edict_t *ent) {
return getShootingCone (ent, origin) >= cr::cosf (cr::deg2rad ((ent->v.fov > 0 ? ent->v.fov : 90.0f) * 0.5f));
}
bool isVisible (const Vector &origin, edict_t *ent) {
if (engine.isNullEntity (ent)) {
bool BotUtils::isVisible (const Vector &origin, edict_t *ent) {
if (game.isNullEntity (ent)) {
return false;
}
TraceResult tr;
engine.testLine (ent->v.origin + ent->v.view_ofs, origin, TRACE_IGNORE_EVERYTHING, ent, &tr);
game.testLine (ent->v.origin + ent->v.view_ofs, origin, TRACE_IGNORE_EVERYTHING, ent, &tr);
if (tr.flFraction != 1.0f) {
return false;
@ -63,81 +106,7 @@ bool isVisible (const Vector &origin, edict_t *ent) {
return true;
}
void showMenu (edict_t *ent, MenuId menu) {
static bool s_menusParsed = false;
// make menus looks like we need only once
if (!s_menusParsed) {
extern void setupBotMenus (void);
setupBotMenus ();
for (int i = 0; i < BOT_MENU_TOTAL_MENUS; i++) {
auto parsed = &g_menus[i];
const String &translated = engine.translate (parsed->text.chars ());
// translate all the things
parsed->text = translated;
// make menu looks best
if (!(g_gameFlags & GAME_LEGACY)) {
for (int j = 0; j < 10; j++) {
parsed->text.replace (format ("%d.", j), format ("\\r%d.\\w", j));
}
}
}
s_menusParsed = true;
}
if (!isPlayer (ent)) {
return;
}
Client &client = g_clients[engine.indexOfEntity (ent) - 1];
if (menu == BOT_MENU_INVALID) {
MessageWriter (MSG_ONE_UNRELIABLE, engine.getMessageId (NETMSG_SHOWMENU), Vector::null (), ent)
.writeShort (0)
.writeChar (0)
.writeByte (0)
.writeString ("");
client.menu = menu;
return;
}
int menuIndex = 0;
for (; menuIndex < BOT_MENU_TOTAL_MENUS; menuIndex++) {
if (g_menus[menuIndex].id == menu) {
break;
}
}
const auto &menuText = g_menus[menuIndex];
const char *text = ((g_gameFlags & (GAME_XASH_ENGINE | GAME_MOBILITY)) && !yb_display_menu_text.boolean ()) ? " " : menuText.text.chars ();
MessageWriter msg;
while (strlen (text) >= 64) {
msg.start (MSG_ONE_UNRELIABLE, engine.getMessageId (NETMSG_SHOWMENU), Vector::null (), ent)
.writeShort (menuText.slots)
.writeChar (-1)
.writeByte (1);
for (int i = 0; i < 64; i++) {
msg.writeChar (text[i]);
}
msg.end ();
text += 64;
}
MessageWriter (MSG_ONE_UNRELIABLE, engine.getMessageId (NETMSG_SHOWMENU), Vector::null (), ent)
.writeShort (menuText.slots)
.writeChar (-1)
.writeByte (0)
.writeString (text);
client.menu = menu;
g_engfuncs.pfnClientCommand (ent, "speak \"player/geiger1\"\n"); // Stops others from hearing menu sounds..
}
void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
void BotUtils::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
// this function draw spraypaint depending on the tracing results.
static StringArray logotypes;
@ -146,18 +115,18 @@ void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
logotypes = String ("{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r").split (";");
}
int entityIndex = -1, message = TE_DECAL;
int decalIndex = g_engfuncs.pfnDecalIndex (logotypes[logotypeIndex].chars ());
int decalIndex = engfuncs.pfnDecalIndex (logotypes[logotypeIndex].chars ());
if (decalIndex < 0) {
decalIndex = g_engfuncs.pfnDecalIndex ("{lambda06");
decalIndex = engfuncs.pfnDecalIndex ("{lambda06");
}
if (trace->flFraction == 1.0f) {
return;
}
if (!engine.isNullEntity (trace->pHit)) {
if (!game.isNullEntity (trace->pHit)) {
if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) {
entityIndex = engine.indexOfEntity (trace->pHit);
entityIndex = game.indexOfEntity (trace->pHit);
}
else {
return;
@ -185,11 +154,11 @@ void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
if (logotypes[logotypeIndex].contains ("{")) {
MessageWriter (MSG_BROADCAST, SVC_TEMPENTITY)
.writeByte (TE_PLAYERDECAL)
.writeByte (engine.indexOfEntity (pev->pContainingEntity))
.writeByte (game.indexOfEntity (pev->pContainingEntity))
.writeCoord (trace->vecEndPos.x)
.writeCoord (trace->vecEndPos.y)
.writeCoord (trace->vecEndPos.z)
.writeShort (static_cast <short> (engine.indexOfEntity (trace->pHit)))
.writeShort (static_cast <short> (game.indexOfEntity (trace->pHit)))
.writeByte (decalIndex);
}
else {
@ -209,185 +178,8 @@ void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) {
}
}
void cleanupGarbage (void) {
// this function free's all allocated memory
waypoints.init (); // frees waypoint data
delete[] g_experienceData;
g_experienceData = nullptr;
}
void updateGlobalExperience (void) {
// this function called after each end of the round to update knowledge about most dangerous waypoints for each team.
// no waypoints, no experience used or waypoints edited or being edited?
if (waypoints.length () < 1 || waypoints.hasChanged ()) {
return; // no action
}
uint16 maxDamage; // maximum damage
uint16 actDamage; // actual damage
int bestIndex; // best index to store
bool recalcKills = false;
// get the most dangerous waypoint for this position for terrorist team
for (int i = 0; i < waypoints.length (); i++) {
maxDamage = 0;
bestIndex = INVALID_WAYPOINT_INDEX;
for (int j = 0; j < waypoints.length (); j++) {
if (i == j) {
continue;
}
actDamage = (g_experienceData + (i * waypoints.length ()) + j)->team0Damage;
if (actDamage > maxDamage) {
maxDamage = actDamage;
bestIndex = j;
}
}
if (maxDamage > MAX_DAMAGE_VALUE) {
recalcKills = true;
}
(g_experienceData + (i * waypoints.length ()) + i)->team0DangerIndex = static_cast <short> (bestIndex);
}
// get the most dangerous waypoint for this position for counter-terrorist team
for (int i = 0; i < waypoints.length (); i++) {
maxDamage = 0;
bestIndex = INVALID_WAYPOINT_INDEX;
for (int j = 0; j < waypoints.length (); j++) {
if (i == j) {
continue;
}
actDamage = (g_experienceData + (i * waypoints.length ()) + j)->team1Damage;
if (actDamage > maxDamage) {
maxDamage = actDamage;
bestIndex = j;
}
}
if (maxDamage > MAX_DAMAGE_VALUE) {
recalcKills = true;
}
(g_experienceData + (i * waypoints.length ()) + i)->team1DangerIndex = static_cast <short> (bestIndex);
}
// adjust values if overflow is about to happen
if (recalcKills) {
for (int i = 0; i < waypoints.length (); i++) {
for (int j = 0; j < waypoints.length (); j++) {
if (i == j) {
continue;
}
int clip = (g_experienceData + (i * waypoints.length ()) + j)->team0Damage;
clip -= static_cast <int> (MAX_DAMAGE_VALUE * 0.5);
if (clip < 0) {
clip = 0;
}
(g_experienceData + (i * waypoints.length ()) + j)->team0Damage = static_cast <uint16> (clip);
clip = (g_experienceData + (i * waypoints.length ()) + j)->team1Damage;
clip -= static_cast <int> (MAX_DAMAGE_VALUE * 0.5);
if (clip < 0) {
clip = 0;
}
(g_experienceData + (i * waypoints.length ()) + j)->team1Damage = static_cast <uint16> (clip);
}
}
}
g_highestKills++;
int clip = g_highestDamageT - static_cast <int> (MAX_DAMAGE_VALUE * 0.5);
if (clip < 1) {
clip = 1;
}
g_highestDamageT = clip;
clip = (int)g_highestDamageCT - static_cast <int> (MAX_DAMAGE_VALUE * 0.5);
if (clip < 1) {
clip = 1;
}
g_highestDamageCT = clip;
if (g_highestKills == MAX_KILL_HISTORY) {
for (int i = 0; i < waypoints.length (); i++) {
(g_experienceData + (i * waypoints.length ()) + i)->team0Damage /= static_cast <uint16> (engine.maxClients () * 0.5);
(g_experienceData + (i * waypoints.length ()) + i)->team1Damage /= static_cast <uint16> (engine.maxClients () * 0.5);
}
g_highestKills = 1;
}
}
void initRound (void) {
// this is called at the start of each round
g_roundEnded = false;
g_canSayBombPlanted = true;
// check team economics
for (int team = 0; team < MAX_TEAM_COUNT; team++) {
bots.updateTeamEconomics (team);
bots.selectLeaders (team, true);
}
bots.reset ();
for (int i = 0; i < engine.maxClients (); i++) {
auto bot = bots.getBot (i);
if (bot != nullptr) {
bot->newRound ();
}
g_radioSelect[i] = 0;
}
waypoints.setBombPos (true);
waypoints.clearVisited ();
g_bombSayString = false;
g_timeBombPlanted = 0.0f;
g_timeNextBombUpdate = 0.0f;
for (int i = 0; i < MAX_TEAM_COUNT; i++) {
g_lastRadioTime[i] = 0.0f;
}
g_botsCanPause = false;
for (int i = 0; i < TASK_MAX; i++) {
g_taskFilters[i].time = 0.0f;
}
updateGlobalExperience (); // update experience data on round start
// calculate the round mid/end in world time
g_timeRoundStart = engine.timebase () + mp_freezetime.flt ();
g_timeRoundMid = g_timeRoundStart + mp_roundtime.flt () * 60.0f * 0.5f;
g_timeRoundEnd = g_timeRoundStart + mp_roundtime.flt () * 60.0f;
}
int getWeaponPenetrationPower (int id) {
// returns if weapon can pierce through a wall
int i = 0;
while (g_weaponSelect[i].id) {
if (g_weaponSelect[i].id == id) {
return g_weaponSelect[i].penetratePower;
}
i++;
}
return 0;
}
bool isPlayer (edict_t *ent) {
if (engine.isNullEntity (ent)) {
bool BotUtils::isPlayer (edict_t *ent) {
if (game.isNullEntity (ent)) {
return false;
}
@ -401,25 +193,25 @@ bool isPlayer (edict_t *ent) {
return false;
}
bool isPlayerVIP (edict_t *ent) {
if (!(g_mapFlags & MAP_AS)) {
bool BotUtils::isPlayerVIP (edict_t *ent) {
if (!game.mapIs (MAP_AS)) {
return false;
}
if (!isPlayer (ent)) {
return false;
}
return *(g_engfuncs.pfnInfoKeyValue (g_engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v';
return *(engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v';
}
bool isFakeClient (edict_t *ent) {
if (bots.getBot (ent) != nullptr || (!engine.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) {
bool BotUtils::isFakeClient (edict_t *ent) {
if (bots.getBot (ent) != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) {
return true;
}
return false;
}
bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) {
bool BotUtils::openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) {
if (outFile->isValid ()) {
outFile->close ();
}
@ -446,11 +238,13 @@ bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *ou
// unload and reopen file using MemoryFile
outFile->open (langConfig);
}
else
else {
outFile->open (format ("%s/lang/en_%s", configDir, fileName));
}
else
}
else {
outFile->open (format ("%s/%s", configDir, fileName));
}
if (!outFile->isValid ()) {
logEntry (true, LL_ERROR, errorIfNotExists);
@ -459,57 +253,31 @@ bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *ou
return true;
}
void checkWelcome (void) {
void BotUtils::checkWelcome (void) {
// the purpose of this function, is to send quick welcome message, to the listenserver entity.
if (engine.isDedicated ())
return;
static bool messageSent = !yb_display_welcome_text.boolean ();
static float receiveTime = 0.0f;
if (messageSent) {
if (game.isDedicated () || !yb_display_welcome_text.boolean () || !m_needToSendWelcome) {
return;
}
m_welcomeReceiveTime = 0.0f;
if (g_gameFlags & GAME_LEGACY) {
g_gameWelcomeSent = true;
if (game.is (GAME_LEGACY)) {
m_needToSendWelcome = true;
return;
}
bool needToSendMsg = (waypoints.length () > 0 ? m_needToSendWelcome : true);
if (isAlive (game.getLocalEntity ()) && m_welcomeReceiveTime < 1.0 && needToSendMsg) {
m_welcomeReceiveTime = game.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing
}
static StringArray sentences;
if (!(g_gameFlags & (GAME_MOBILITY | GAME_XASH_ENGINE)) && sentences.empty ()) {
// add default messages
sentences.push ("hello user,communication is acquired");
sentences.push ("your presence is acknowledged");
sentences.push ("high man, your in command now");
sentences.push ("blast your hostile for good");
sentences.push ("high man, kill some idiot here");
sentences.push ("is there a doctor in the area");
sentences.push ("warning, experimental materials detected");
sentences.push ("high amigo, shoot some but");
sentences.push ("attention, hours of work software, detected");
sentences.push ("time for some bad ass explosion");
sentences.push ("bad ass son of a breach device activated");
sentences.push ("high, do not question this great service");
sentences.push ("engine is operative, hello and goodbye");
sentences.push ("high amigo, your administration has been great last day");
sentences.push ("attention, expect experimental armed hostile presence");
sentences.push ("warning, medical attention required");
if (m_welcomeReceiveTime > 0.0f && needToSendMsg) {
if (!game.is (GAME_MOBILITY | GAME_XASH_ENGINE)) {
game.execCmd ("speak \"%s\"", m_sentences.random ().chars ());
}
bool needToSendMsg = (waypoints.length () > 0 ? g_gameWelcomeSent : true);
game.chatPrint ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL);
if (isAlive (g_hostEntity) && receiveTime < 1.0 && needToSendMsg) {
receiveTime = engine.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing
}
if (receiveTime > 0.0f && receiveTime < engine.timebase () && needToSendMsg) {
if (!(g_gameFlags & (GAME_MOBILITY | GAME_XASH_ENGINE))) {
engine.execCmd ("speak \"%s\"", sentences.random ().chars ());
}
engine.chatPrint ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL);
MessageWriter (MSG_ONE, SVC_TEMPENTITY, Vector::null (), g_hostEntity)
MessageWriter (MSG_ONE, SVC_TEMPENTITY, Vector::null (), game.getLocalEntity ())
.writeByte (TE_TEXTMESSAGE)
.writeByte (1)
.writeShort (MessageWriter::fs16 (-1, 1 << 13))
@ -529,12 +297,12 @@ void checkWelcome (void) {
.writeShort (MessageWriter::fu16 (0.1f, 1 << 8))
.writeString (format ("\nServer is running %s v%s (Build: %u)\nDeveloped by %s\n\n%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_AUTHOR, waypoints.getAuthor ()));
receiveTime = 0.0;
messageSent = true;
m_welcomeReceiveTime = 0.0f;
m_needToSendWelcome = false;
}
}
void logEntry (bool outputToConsole, int logLevel, const char *format, ...) {
void BotUtils::logEntry (bool outputToConsole, int logLevel, const char *format, ...) {
// this function logs a message to the message log file root directory.
va_list ap;
@ -563,7 +331,7 @@ void logEntry (bool outputToConsole, int logLevel, const char *format, ...) {
}
if (outputToConsole) {
engine.print ("%s%s", levelString, buffer);
game.print ("%s%s", levelString, buffer);
}
// now check if logging disabled
@ -599,7 +367,7 @@ void logEntry (bool outputToConsole, int logLevel, const char *format, ...) {
if (logLevel == LL_FATAL) {
bots.kickEveryone (true);
cleanupGarbage ();
waypoints.init ();
#if defined(PLATFORM_WIN32)
DestroyWindow (GetForegroundWindow ());
@ -616,7 +384,7 @@ void logEntry (bool outputToConsole, int logLevel, const char *format, ...) {
}
}
bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool isAlive, bool needDrawn, bool needBotWithC4) {
bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool needAlive, bool needDrawn, bool needBotWithC4) {
// this function finds nearest to to, player with set of parameters, like his
// team, live status, search distance etc. if needBot is true, then pvHolder, will
// be filled with bot pointer, else with edict pointer(!).
@ -624,16 +392,14 @@ bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool
edict_t *survive = nullptr; // pointer to temporally & survive entity
float nearestPlayer = 4096.0f; // nearest player
int toTeam = engine.getTeam (to);
for (int i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
int toTeam = game.getTeam (to);
for (const auto &client : m_clients) {
if (!(client.flags & CF_USED) || client.ent == to) {
continue;
}
if ((sameTeam && client.team != toTeam) || (isAlive && !(client.flags & CF_ALIVE)) || (needBot && !isFakeClient (client.ent)) || (needDrawn && (client.ent->v.effects & EF_NODRAW)) || (needBotWithC4 && (client.ent->v.weapons & WEAPON_C4))) {
if ((sameTeam && client.team != toTeam) || (needAlive && !(client.flags & CF_ALIVE)) || (needBot && !isFakeClient (client.ent)) || (needDrawn && (client.ent->v.effects & EF_NODRAW)) || (needBotWithC4 && (client.ent->v.weapons & WEAPON_C4))) {
continue; // filter players with parameters
}
float distance = (client.ent->v.origin - to->v.origin).length ();
@ -644,8 +410,9 @@ bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool
}
}
if (engine.isNullEntity (survive))
if (game.isNullEntity (survive)) {
return false; // nothing found
}
// fill the holder
if (needBot) {
@ -657,26 +424,26 @@ bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool
return true;
}
void attachSoundsToClients (edict_t *ent, const char *sample, float volume) {
void BotUtils::attachSoundsToClients (edict_t *ent, const char *sample, float volume) {
// this function called by the sound hooking code (in emit_sound) enters the played sound into
// the array associated with the entity
if (engine.isNullEntity (ent) || isEmptyStr (sample)) {
if (game.isNullEntity (ent) || isEmptyStr (sample)) {
return;
}
const Vector &origin = engine.getAbsPos (ent);
const Vector &origin = game.getAbsPos (ent);
if (origin.empty ()) {
return;
}
int index = engine.indexOfEntity (ent) - 1;
int index = game.indexOfEntity (ent) - 1;
if (index < 0 || index >= engine.maxClients ()) {
if (index < 0 || index >= game.maxClients ()) {
float nearestDistance = 99999.0f;
// loop through all players
for (int i = 0; i < engine.maxClients (); i++) {
const Client &client = g_clients[i];
for (int i = 0; i < game.maxClients (); i++) {
const Client &client = m_clients[i];
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE)) {
continue;
@ -692,63 +459,63 @@ void attachSoundsToClients (edict_t *ent, const char *sample, float volume) {
}
// in case of worst case
if (index < 0 || index >= engine.maxClients ()) {
if (index < 0 || index >= game.maxClients ()) {
return;
}
Client &client = g_clients[index];
Client &client = m_clients[index];
if (strncmp ("player/bhit_flesh", sample, 17) == 0 || strncmp ("player/headshot", sample, 15) == 0) {
// hit/fall sound?
client.hearingDistance = 768.0f * volume;
client.timeSoundLasting = engine.timebase () + 0.5f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 0.5f;
client.sound = origin;
}
else if (strncmp ("items/gunpickup", sample, 15) == 0) {
// weapon pickup?
client.hearingDistance = 768.0f * volume;
client.timeSoundLasting = engine.timebase () + 0.5f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 0.5f;
client.sound = origin;
}
else if (strncmp ("weapons/zoom", sample, 12) == 0) {
// sniper zooming?
client.hearingDistance = 512.0f * volume;
client.timeSoundLasting = engine.timebase () + 0.1f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 0.1f;
client.sound = origin;
}
else if (strncmp ("items/9mmclip", sample, 13) == 0) {
// ammo pickup?
client.hearingDistance = 512.0f * volume;
client.timeSoundLasting = engine.timebase () + 0.1f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 0.1f;
client.sound = origin;
}
else if (strncmp ("hostage/hos", sample, 11) == 0) {
// CT used hostage?
client.hearingDistance = 1024.0f * volume;
client.timeSoundLasting = engine.timebase () + 5.0f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 5.0f;
client.sound = origin;
}
else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) {
// broke something?
client.hearingDistance = 1024.0f * volume;
client.timeSoundLasting = engine.timebase () + 2.0f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 2.0f;
client.sound = origin;
}
else if (strncmp ("doors/doormove", sample, 14) == 0) {
// someone opened a door
client.hearingDistance = 1024.0f * volume;
client.timeSoundLasting = engine.timebase () + 3.0f;
client.soundPos = origin;
client.timeSoundLasting = game.timebase () + 3.0f;
client.sound = origin;
}
}
void simulateSoundUpdates (int playerIndex) {
void BotUtils::simulateSoundUpdates (int playerIndex) {
// this function tries to simulate playing of sounds to let the bots hear sounds which aren't
// captured through server sound hooking
if (playerIndex < 0 || playerIndex >= engine.maxClients ()) {
if (playerIndex < 0 || playerIndex >= game.maxClients ()) {
return; // reliability check
}
Client &client = g_clients[playerIndex];
Client &client = m_clients[playerIndex];
float hearDistance = 0.0f;
float timeSound = 0.0f;
@ -756,23 +523,23 @@ void simulateSoundUpdates (int playerIndex) {
if (client.ent->v.oldbuttons & IN_ATTACK) // pressed attack button?
{
hearDistance = 2048.0f;
timeSound = engine.timebase () + 0.3f;
timeSound = game.timebase () + 0.3f;
}
else if (client.ent->v.oldbuttons & IN_USE) // pressed used button?
{
hearDistance = 512.0f;
timeSound = engine.timebase () + 0.5f;
timeSound = game.timebase () + 0.5f;
}
else if (client.ent->v.oldbuttons & IN_RELOAD) // pressed reload button?
{
hearDistance = 512.0f;
timeSound = engine.timebase () + 0.5f;
timeSound = game.timebase () + 0.5f;
}
else if (client.ent->v.movetype == MOVETYPE_FLY) // uses ladder?
{
if (cr::abs (client.ent->v.velocity.z) > 50.0f) {
hearDistance = 1024.0f;
timeSound = engine.timebase () + 0.3f;
timeSound = game.timebase () + 0.3f;
}
}
else {
@ -781,7 +548,7 @@ void simulateSoundUpdates (int playerIndex) {
if (mp_footsteps.boolean ()) {
// moves fast enough?
hearDistance = 1280.0f * (client.ent->v.velocity.length2D () / 260.0f);
timeSound = engine.timebase () + 0.3f;
timeSound = game.timebase () + 0.3f;
}
}
@ -790,23 +557,52 @@ void simulateSoundUpdates (int playerIndex) {
}
// some sound already associated
if (client.timeSoundLasting > engine.timebase ()) {
if (client.timeSoundLasting > game.timebase ()) {
if (client.hearingDistance <= hearDistance) {
// override it with new
client.hearingDistance = hearDistance;
client.timeSoundLasting = timeSound;
client.soundPos = client.ent->v.origin;
client.sound = client.ent->v.origin;
}
}
else {
// just remember it
client.hearingDistance = hearDistance;
client.timeSoundLasting = timeSound;
client.soundPos = client.ent->v.origin;
client.sound = client.ent->v.origin;
}
}
int buildNumber (void) {
void BotUtils::updateClients (void) {
// record some stats of all players on the server
for (int i = 0; i < game.maxClients (); i++) {
edict_t *player = game.entityOfIndex (i + 1);
Client &client = m_clients[i];
if (!game.isNullEntity (player) && (player->v.flags & FL_CLIENT)) {
client.ent = player;
client.flags |= CF_USED;
if (util.isAlive (player)) {
client.flags |= CF_ALIVE;
}
else {
client.flags &= ~CF_ALIVE;
}
if (client.flags & CF_ALIVE) {
client.origin = player->v.origin;
simulateSoundUpdates (i);
}
}
else {
client.flags &= ~(CF_USED | CF_ALIVE);
client.ent = nullptr;
}
}
}
int BotUtils::buildNumber (void) {
// this function generates build number from the compiler date macros
static int buildNumber = 0;
@ -848,7 +644,7 @@ int buildNumber (void) {
return buildNumber;
}
int getWeaponData (bool needString, const char *weaponAlias, int weaponIndex) {
int BotUtils::getWeaponAlias (bool needString, const char *weaponAlias, int weaponIndex) {
// this function returning weapon id from the weapon alias and vice versa.
// structure definition for weapon tab
@ -895,18 +691,18 @@ int getWeaponData (bool needString, const char *weaponAlias, int weaponIndex) {
// if we need to return the string, find by weapon id
if (needString && weaponIndex != -1) {
for (size_t i = 0; i < cr::arrsize (weaponTab); i++) {
if (weaponTab[i].weaponIndex == weaponIndex) { // is weapon id found?
return MAKE_STRING (weaponTab[i].alias);
for (auto &tab : weaponTab) {
if (tab.weaponIndex == weaponIndex) { // is weapon id found?
return MAKE_STRING (tab.alias);
}
}
return MAKE_STRING ("(none)"); // return none
}
// else search weapon by name and return weapon id
for (size_t i = 0; i < cr::arrsize (weaponTab); i++) {
if (strncmp (weaponTab[i].alias, weaponAlias, strlen (weaponTab[i].alias)) == 0) {
return weaponTab[i].weaponIndex;
for (auto &tab : weaponTab) {
if (strncmp (tab.alias, weaponAlias, strlen (tab.alias)) == 0) {
return tab.weaponIndex;
}
}
return -1; // no weapon was found return -1

File diff suppressed because it is too large Load diff