Many small fixes to combat behaviour, navigation and perfomance.

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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