2.9 Update (#64)
* Fixed bots not camping in camp spots. Fixed chatter/radio message cycling. (need feedback). Fixed CTs unable to defuse bomb. Fixed backward jump path generation in waypoint editor. Fixed autoradius in waypoint editor. Fixed autoradius menu non closeable. Fixed bots version display on entering game. Fixed memory leak in DLL-loader. (non metamod). Fixed bots able to see through smoke. Fixed team-detection on non-standard modes. Fixed quota & autovacate management. Fixed bunch of warnings from static analyzers. Greatly imporoved grenade throwing. Grealty reduced bot CPU usage. * Fixed stack-corruption in memory-file reader. Fixed A* pathfinder not working correctly. Fixed 'Tried to write to uninitialized sizebuf_t error' on bot add/remove. Minor tweaks to camping and bot enemy aiming * Make clang happy. * Fixed VIP-dection on some maps. Fixed occupied waypoint checker. Small refactoring of code with clang-format. * Fixed clang compilation * Fixed compilation. * Debugging seek cover task. Some more code cleanup. * Fixed typos. * Fixes to attack movement. Revert Z component updates. * Fixes for aiming at enemy. Fixes for seek cover & enemy hunt tasks. More refactoring. * Making clang happy once again? Tweaked grenade timers. * Revised language comparer hasher * Fixed build. * Fixed build. * Optimized headshot offsets. Optimized aim errors and enemy searches. Get rid of preprocessor macroses. Added back yb_think_fps. Use with caution. * Minor refactoring of code. * Check if tracking entity is still alive. Do not duck in crouch-goal waypoints. Remove ancient hack with failed goals. * Get rid of c++14 stuff. Tweaked isOccupiedPoint. * Changed pickup check radius. * Fix compilation. * Fixed bots ignore breakables. Fixed A* pathfinder. Fixed searching for optimal waypoints. Fixed bot waypoint reachability functions. * Get rid of new/delete calls in pathfinder. Disallow access to yapb waypoint menu on hlds. Minor refactoring. * Updated linux/osx makefile * Spaces -> Tabs in makefile. Made G++ happy. * Updated makefile. * Fixed heap buffer overflow in config loader code. * Lowered CPU usage a bit, by using "waypoint buckets" for searching closest node. Do not traceline for doors on map, that have no doors. Get rid stack-based containers. * Remove win-only debug crap. * Refactored string class. * Fix OSX compiling. * Minor refactoring of corelib to use cpp move-semantic. * Use reference for active grenades searcher. * Use system's atan2f () as it's eror rate is a bit lower. Fixed bots continuously stays in throw smoke task. Fixed bots reaching camp-goal jumping or stays they for some time. Increased radius for searching targets for grenades. Tweaked bot difficulty levels. Improved sniper weapon handling. Trying to stand still while shooting. Increase retreat level only if sniper weapon is low on ammo. Fixed predict path enemy tracking timer is always true. Allow bots to process their tasks while on freezetime, so on small maps they already aiming enemies when freezetime ends. Fied bots endlessy trying to pickup weapons. Reduce surpise timers when holding sniper weapons. New aim-at-head position calculation. Shoot delay timers are now based on bot's difficulty. Prefer smoke grenades more than flashbangs. Fixed kill-all bot command not killing one random bot for first time use. Do not play with jump velocity, now using the same as in waypoints. Tweaked shift move, so zero move speed not overriden with shift speed. Radius waypoint searcher use waypoint bucket as well. Increase reachability radius for dest waypoint, if it's currenlty owned by other bot. Partially fixed bots choice to use unreachable waypoints. * Makes OSX clang happy? * Support for compiling on llvm-win32, makefile to be done. Increased default reachability time. * Fixed build. * Move level-initialization stuff from Spawn to ServerActivate, so bot will not check init-stuff every entity spawn. This should save few CPU cycles. * Fixed active grenades list not working after changelevel. Reworked items pickup code, so every bot is not firing sphere search every time, but instead we maintain our own list of intresting entities, so every bot is accessing this list. This should lower CPU usage more a little. * Precache should be done in spawn... * Do not use engfuncs in intresting entities. * Fixed GCC-8.2 warnings. Minor refactoring. * Added some safety checks to intresting entities. Get rid of stdc++ dependency for GCC & ICC under linux. * Remove -g from release make. Cosmetic changes. * Re-enabled debug overlay. * Remove test header... * Some static-analyzer warnings fixed. Support for X64 build for FWGS Xash3D Engine. * Reduced time between selecting grenade and throwing it away. Do not try to kill bots that already dead with kill command. Several fixes from static-analyzers. * Update CI. * Fixed bot's not added after the changelevel on Xash3D engine. * Revert commit that enables movement during freezetime. Everything goes bad, when there is no freezetime.... * Bots will try to not strafe while in combat if seeing enemy only partially. Do not use "shift" when considering stuck. * Weapon price for Elite is 800$ since CS 1.6... * Fixed bots at difficulty 0 can't shoot enemies. * Cosmetic change. * Fixed assert in ClientDisconnect when quitting game while meta unloaded yapb module. Consider freed entities as invalid. * Bigger distance for throwing he grenades. * Faster version of atan2f(). * Removed accidentally left SSE header. * Cosmetic changes to enums. * Tweaked difficulty levels. Bots on Android will have a difficulty level 2 by default. Fixed LTO builds under linux. * Do not consider Android CS as legacy. * Get rid of system's math functions. Just for fun) * Use SSE2 for sincos function. * Fixed failed during load wayponts still allows to add bots, thus causing bot to crash. Added ability to delete waypoint by number using "yb wp delete". Enabled Link Time Optimization for Linux and OSX. * Fixed CI Builds.
This commit is contained in:
parent
faa45c6331
commit
5f6a1638d6
32 changed files with 13626 additions and 17110 deletions
|
|
@ -4,142 +4,141 @@
|
|||
//
|
||||
// This software is licensed under the BSD-style license.
|
||||
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
||||
// https://yapb.jeefo.net/license
|
||||
// https://yapb.ru/license
|
||||
//
|
||||
|
||||
#include <core.h>
|
||||
#include <yapb.h>
|
||||
|
||||
ConVar yb_chat ("yb_chat", "1");
|
||||
|
||||
void StripTags (char *buffer)
|
||||
{
|
||||
void stripClanTags (char *buffer) {
|
||||
// this function strips 'clan' tags specified below in given string buffer
|
||||
|
||||
const char *tagOpen[] = {"-=", "-[", "-]", "-}", "-{", "<[", "<]", "[-", "]-", "{-", "}-", "[[", "[", "{", "]", "}", "<", ">", "-", "|", "=", "+", "(", ")"};
|
||||
const char *tagClose[] = {"=-", "]-", "[-", "{-", "}-", "]>", "[>", "-]", "-[", "-}", "-{", "]]", "]", "}", "[", "{", ">", "<", "-", "|", "=", "+", ")", "("};
|
||||
if (isEmptyStr (buffer)) {
|
||||
return;
|
||||
}
|
||||
size_t nameLength = strlen (buffer);
|
||||
|
||||
int index, fieldStart, fieldStop, i;
|
||||
int length = strlen (buffer); // get length of string
|
||||
if (nameLength < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// foreach known tag...
|
||||
for (index = 0; index < ARRAYSIZE_HLSDK (tagOpen); index++)
|
||||
{
|
||||
fieldStart = strstr (buffer, tagOpen[index]) - buffer; // look for a tag start
|
||||
constexpr const char *open[] = { "-=", "-[", "-]", "-}", "-{", "<[", "<]", "[-", "]-", "{-", "}-", "[[", "[", "{", "]", "}", "<", ">", "-", "|", "=", "+", "(", ")" };
|
||||
constexpr const char *close[] = { "=-", "]-", "[-", "{-", "}-", "]>", "[>", "-]", "-[", "-}", "-{", "]]", "]", "}", "[", "{", ">", "<", "-", "|", "=", "+", ")", "(" };
|
||||
|
||||
// for each known tag...
|
||||
for (size_t i = 0; i < cr::arrsize (open); i++) {
|
||||
size_t start = strstr (buffer, open[i]) - buffer; // look for a tag start
|
||||
|
||||
// have we found a tag start?
|
||||
if (fieldStart >= 0 && fieldStart < 32)
|
||||
{
|
||||
fieldStop = strstr (buffer, tagClose[index]) - buffer; // look for a tag stop
|
||||
if (start < 32) {
|
||||
size_t stop = strstr (buffer, close[i]) - buffer; // look for a tag stop
|
||||
|
||||
// have we found a tag stop?
|
||||
if (fieldStop > fieldStart && fieldStop < 32)
|
||||
{
|
||||
int tagLength = strlen (tagClose[index]);
|
||||
if (stop > start && stop < 32) {
|
||||
size_t tag = strlen (close[i]);
|
||||
size_t j = start;
|
||||
|
||||
for (i = fieldStart; i < length - (fieldStop + tagLength - fieldStart); i++)
|
||||
buffer[i] = buffer[i + (fieldStop + tagLength - fieldStart)]; // overwrite the buffer with the stripped string
|
||||
|
||||
buffer[i] = 0x0; // terminate the string
|
||||
for (; j < nameLength - (stop + tag - start); j++) {
|
||||
buffer[j] = buffer[j + (stop + tag - start)]; // overwrite the buffer with the stripped string
|
||||
}
|
||||
buffer[j] = '\0'; // terminate the string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// have we stripped too much (all the stuff)?
|
||||
if (buffer[0] != '\0')
|
||||
{
|
||||
String::TrimExternalBuffer (buffer); // if so, string is just a tag
|
||||
if (isEmptyStr (buffer)) {
|
||||
String::trimChars (buffer);
|
||||
return;
|
||||
}
|
||||
String::trimChars (buffer); // if so, string is just a tag
|
||||
|
||||
int tagLength = 0;
|
||||
// strip just the tag part...
|
||||
for (size_t i = 0; i < cr::arrsize (open); i++) {
|
||||
size_t start = strstr (buffer, open[i]) - buffer; // look for a tag start
|
||||
|
||||
// strip just the tag part...
|
||||
for (index = 0; index < ARRAYSIZE_HLSDK (tagOpen); index++)
|
||||
{
|
||||
fieldStart = strstr (buffer, tagOpen[index]) - buffer; // look for a tag start
|
||||
// have we found a tag start?
|
||||
if (start < 32) {
|
||||
size_t tag = strlen (open[i]);
|
||||
size_t j = start;
|
||||
|
||||
// have we found a tag start?
|
||||
if (fieldStart >= 0 && fieldStart < 32)
|
||||
{
|
||||
tagLength = strlen (tagOpen[index]);
|
||||
for (; j < nameLength - tag; j++) {
|
||||
buffer[j] = buffer[j + tag]; // overwrite the buffer with the stripped string
|
||||
}
|
||||
buffer[j] = '\0'; // terminate the string
|
||||
start = strstr (buffer, close[i]) - buffer; // look for a tag stop
|
||||
|
||||
for (i = fieldStart; i < length - tagLength; i++)
|
||||
buffer[i] = buffer[i + tagLength]; // overwrite the buffer with the stripped string
|
||||
// have we found a tag stop ?
|
||||
if (start < 32) {
|
||||
tag = strlen (close[i]);
|
||||
j = start;
|
||||
|
||||
buffer[i] = 0x0; // terminate the string
|
||||
|
||||
fieldStart = strstr (buffer, tagClose[index]) - buffer; // look for a tag stop
|
||||
|
||||
// have we found a tag stop ?
|
||||
if (fieldStart >= 0 && fieldStart < 32)
|
||||
{
|
||||
tagLength = strlen (tagClose[index]);
|
||||
|
||||
for (i = fieldStart; i < length - tagLength; i++)
|
||||
buffer[i] = buffer[i + tagLength]; // overwrite the buffer with the stripped string
|
||||
|
||||
buffer[i] = 0; // terminate the string
|
||||
for (; j < nameLength - tag; j++) {
|
||||
buffer[j] = buffer[j + tag]; // overwrite the buffer with the stripped string
|
||||
}
|
||||
buffer[j] = 0; // terminate the string
|
||||
}
|
||||
}
|
||||
}
|
||||
String::TrimExternalBuffer (buffer); // to finish, strip eventual blanks after and before the tag marks
|
||||
String::trimChars (buffer); // to finish, strip eventual blanks after and before the tag marks
|
||||
}
|
||||
|
||||
char *HumanizeName (char *name)
|
||||
{
|
||||
char *humanizeName (char *name) {
|
||||
// this function humanize player name (i.e. trim clan and switch to lower case (sometimes))
|
||||
|
||||
static char outputName[64]; // create return name buffer
|
||||
strncpy (outputName, name, SIZEOF_CHAR (outputName)); // copy name to new buffer
|
||||
strncpy (outputName, name, cr::bufsize (outputName)); // copy name to new buffer
|
||||
|
||||
// drop tag marks, 80 percent of time
|
||||
if (Random.Int (1, 100) < 80)
|
||||
StripTags (outputName);
|
||||
else
|
||||
String::TrimExternalBuffer (outputName);
|
||||
if (rng.getInt (1, 100) < 80) {
|
||||
stripClanTags (outputName);
|
||||
}
|
||||
else {
|
||||
String::trimChars (outputName);
|
||||
}
|
||||
|
||||
// sometimes switch name to lower characters
|
||||
// note: since we're using russian names written in english, we reduce this shit to 6 percent
|
||||
if (Random.Int (1, 100) <= 6)
|
||||
{
|
||||
for (int i = 0; i < static_cast <int> (strlen (outputName)); i++)
|
||||
if (rng.getInt (1, 100) <= 6) {
|
||||
for (size_t i = 0; i < strlen (outputName); i++) {
|
||||
outputName[i] = static_cast <char> (tolower (static_cast <int> (outputName[i]))); // to lower case
|
||||
}
|
||||
}
|
||||
return &outputName[0]; // return terminated string
|
||||
}
|
||||
|
||||
void HumanizeChat (char *buffer)
|
||||
{
|
||||
void addChatErrors (char *buffer) {
|
||||
// this function humanize chat string to be more handwritten
|
||||
|
||||
int length = strlen (buffer); // get length of string
|
||||
int i = 0;
|
||||
size_t length = strlen (buffer); // get length of string
|
||||
size_t i = 0;
|
||||
|
||||
// sometimes switch text to lowercase
|
||||
// note: since we're using russian chat written in english, we reduce this shit to 4 percent
|
||||
if (Random.Int (1, 100) <= 4)
|
||||
{
|
||||
for (i = 0; i < length; i++)
|
||||
buffer[i] = static_cast <char> (tolower (static_cast <int> (buffer[i])));; // switch to lowercase
|
||||
// note: since we're using russian chat written in English, we reduce this shit to 4 percent
|
||||
if (rng.getInt (1, 100) <= 4) {
|
||||
for (i = 0; i < length; i++) {
|
||||
buffer[i] = static_cast <char> (tolower (static_cast <int> (buffer[i]))); // switch to lowercase
|
||||
}
|
||||
}
|
||||
|
||||
if (length > 15)
|
||||
{
|
||||
if (length > 15) {
|
||||
size_t percentile = length / 2;
|
||||
|
||||
// "length / 2" percent of time drop a character
|
||||
if (Random.Int (1, 100) < (length / 2))
|
||||
{
|
||||
int pos = Random.Int ((length / 8), length - (length / 8)); // chose random position in string
|
||||
if (rng.getInt (1u, 100u) < percentile) {
|
||||
size_t pos = rng.getInt (length / 8, length - length / 8); // chose random position in string
|
||||
|
||||
for (i = pos; i < length - 1; i++)
|
||||
for (i = pos; i < length - 1; i++) {
|
||||
buffer[i] = buffer[i + 1]; // overwrite the buffer with stripped string
|
||||
|
||||
buffer[i] = 0x0; // terminate string;
|
||||
}
|
||||
buffer[i] = '\0'; // terminate string;
|
||||
length--; // update new string length
|
||||
}
|
||||
|
||||
// "length" / 4 precent of time swap character
|
||||
if (Random.Int (1, 100) < (length / 4))
|
||||
{
|
||||
int pos = Random.Int ((length / 8), ((3 * length) / 8)); // choose random position in string
|
||||
if (rng.getInt (1u, 100u) < percentile / 2) {
|
||||
size_t pos = rng.getInt (length / 8, 3 * length / 8); // choose random position in string
|
||||
char ch = buffer[pos]; // swap characters
|
||||
|
||||
buffer[pos] = buffer[pos + 1];
|
||||
|
|
@ -149,304 +148,286 @@ void HumanizeChat (char *buffer)
|
|||
buffer[length] = 0; // terminate string
|
||||
}
|
||||
|
||||
void Bot::PrepareChatMessage (char *text)
|
||||
{
|
||||
void Bot::prepareChatMessage (char *text) {
|
||||
// this function parses messages from the botchat, replaces keywords and converts names into a more human style
|
||||
|
||||
if (!yb_chat.GetBool () || IsNullString (text))
|
||||
if (!yb_chat.boolean () || isEmptyStr (text)) {
|
||||
return;
|
||||
|
||||
#define ASSIGN_TALK_ENTITY() if (!engine.IsNullEntity (talkEntity)) strncat (m_tempStrings, HumanizeName (const_cast <char *> (STRING (talkEntity->v.netname))), SIZEOF_CHAR (m_tempStrings))
|
||||
|
||||
memset (&m_tempStrings, 0, sizeof (m_tempStrings));
|
||||
}
|
||||
m_tempStrings.clear ();
|
||||
|
||||
char *textStart = text;
|
||||
char *pattern = text;
|
||||
|
||||
edict_t *talkEntity = nullptr;
|
||||
|
||||
while (pattern != nullptr)
|
||||
{
|
||||
auto getHumanizedName = [] (edict_t *ent) {
|
||||
if (!engine.isNullEntity (ent)) {
|
||||
return const_cast <const char *> (humanizeName (const_cast <char *> (STRING (ent->v.netname))));
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
while (pattern != nullptr) {
|
||||
// all replacement placeholders start with a %
|
||||
pattern = strstr (textStart, "%");
|
||||
pattern = strchr (textStart, '%');
|
||||
|
||||
if (pattern != nullptr)
|
||||
{
|
||||
int length = pattern - textStart;
|
||||
|
||||
if (length > 0)
|
||||
strncpy (m_tempStrings, textStart, length);
|
||||
if (pattern != nullptr) {
|
||||
size_t length = pattern - textStart;
|
||||
|
||||
if (length > 0) {
|
||||
m_tempStrings = String (textStart, length);
|
||||
}
|
||||
pattern++;
|
||||
|
||||
// player with most frags?
|
||||
if (*pattern == 'f')
|
||||
{
|
||||
if (*pattern == 'f') {
|
||||
int highestFrags = -9000; // just pick some start value
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < engine.MaxClients (); i++)
|
||||
{
|
||||
for (int i = 0; i < engine.maxClients (); i++) {
|
||||
const Client &client = g_clients[i];
|
||||
|
||||
if (!(client.flags & CF_USED) || client.ent == GetEntity ())
|
||||
if (!(client.flags & CF_USED) || client.ent == ent ()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
int frags = static_cast <int> (client.ent->v.frags);
|
||||
|
||||
if (frags > highestFrags)
|
||||
{
|
||||
if (frags > highestFrags) {
|
||||
highestFrags = frags;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
talkEntity = g_clients[index].ent;
|
||||
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
// mapname?
|
||||
else if (*pattern == 'm')
|
||||
strncat (m_tempStrings, engine.GetMapName (), SIZEOF_CHAR (m_tempStrings));
|
||||
else if (*pattern == 'm') {
|
||||
m_tempStrings += engine.getMapName ();
|
||||
}
|
||||
// roundtime?
|
||||
else if (*pattern == 'r')
|
||||
{
|
||||
int time = static_cast <int> (g_timeRoundEnd - engine.Time ());
|
||||
strncat (m_tempStrings, FormatBuffer ("%02d:%02d", time / 60, time % 60), SIZEOF_CHAR (m_tempStrings));
|
||||
else if (*pattern == 'r') {
|
||||
int time = static_cast <int> (g_timeRoundEnd - engine.timebase ());
|
||||
m_tempStrings.formatAppend ("%02d:%02d", time / 60, time % 60);
|
||||
}
|
||||
// chat reply?
|
||||
else if (*pattern == 's')
|
||||
{
|
||||
talkEntity = engine.EntityOfIndex (m_sayTextBuffer.entityIndex);
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
else if (*pattern == 's') {
|
||||
talkEntity = engine.entityOfIndex (m_sayTextBuffer.entityIndex);
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
// teammate alive?
|
||||
else if (*pattern == 't')
|
||||
{
|
||||
else if (*pattern == 't') {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < engine.MaxClients (); i++)
|
||||
{
|
||||
for (i = 0; i < engine.maxClients (); i++) {
|
||||
const Client &client = g_clients[i];
|
||||
|
||||
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == GetEntity ())
|
||||
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < engine.MaxClients ())
|
||||
{
|
||||
if (!engine.IsNullEntity (pev->dmg_inflictor) && m_team == engine.GetTeam (pev->dmg_inflictor))
|
||||
if (i < engine.maxClients ()) {
|
||||
if (isPlayer (pev->dmg_inflictor) && m_team == engine.getTeam (pev->dmg_inflictor)) {
|
||||
talkEntity = pev->dmg_inflictor;
|
||||
else
|
||||
}
|
||||
else {
|
||||
talkEntity = g_clients[i].ent;
|
||||
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
}
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
else // no teammates alive...
|
||||
{
|
||||
for (i = 0; i < engine.MaxClients (); i++)
|
||||
{
|
||||
for (i = 0; i < engine.maxClients (); i++) {
|
||||
const Client &client = g_clients[i];
|
||||
|
||||
if (!(client.flags & CF_USED) || client.team != m_team || client.ent == GetEntity ())
|
||||
if (!(client.flags & CF_USED) || client.team != m_team || client.ent == ent ()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < engine.MaxClients ())
|
||||
{
|
||||
if (i < engine.maxClients ()) {
|
||||
talkEntity = g_clients[i].ent;
|
||||
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (*pattern == 'e')
|
||||
{
|
||||
else if (*pattern == 'e') {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < engine.MaxClients (); i++)
|
||||
{
|
||||
for (i = 0; i < engine.maxClients (); i++) {
|
||||
const Client &client = g_clients[i];
|
||||
|
||||
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == GetEntity ())
|
||||
if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == ent ()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < engine.MaxClients ())
|
||||
{
|
||||
if (i < engine.maxClients ()) {
|
||||
talkEntity = g_clients[i].ent;
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
else // no teammates alive...
|
||||
{
|
||||
for (i = 0; i < engine.MaxClients (); i++)
|
||||
{
|
||||
|
||||
// no teammates alive ?
|
||||
else {
|
||||
for (i = 0; i < engine.maxClients (); i++) {
|
||||
const Client &client = g_clients[i];
|
||||
|
||||
if (!(client.flags & CF_USED) || client.team == m_team || client.ent == GetEntity ())
|
||||
if (!(client.flags & CF_USED) || client.team == m_team || client.ent == ent ()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (i < engine.MaxClients ())
|
||||
{
|
||||
if (i < engine.maxClients ()) {
|
||||
talkEntity = g_clients[i].ent;
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (*pattern == 'd')
|
||||
{
|
||||
if (g_gameFlags & GAME_CZERO)
|
||||
{
|
||||
if (Random.Int (1, 100) < 30)
|
||||
strcat (m_tempStrings, "CZ");
|
||||
else
|
||||
strcat (m_tempStrings, "Condition Zero");
|
||||
else if (*pattern == 'd') {
|
||||
if (g_gameFlags & GAME_CZERO) {
|
||||
if (rng.getInt (1, 100) < 30) {
|
||||
m_tempStrings += "CZ";
|
||||
}
|
||||
else {
|
||||
m_tempStrings += "Condition Zero";
|
||||
}
|
||||
}
|
||||
else if ((g_gameFlags & GAME_CSTRIKE16) || (g_gameFlags & GAME_LEGACY))
|
||||
{
|
||||
if (Random.Int (1, 100) < 30)
|
||||
strcat (m_tempStrings, "CS");
|
||||
else
|
||||
strcat (m_tempStrings, "Counter-Strike");
|
||||
else if ((g_gameFlags & GAME_CSTRIKE16) || (g_gameFlags & GAME_LEGACY)) {
|
||||
if (rng.getInt (1, 100) < 30) {
|
||||
m_tempStrings += "CS";
|
||||
}
|
||||
else {
|
||||
m_tempStrings += "Counter-Strike";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (*pattern == 'v')
|
||||
{
|
||||
else if (*pattern == 'v') {
|
||||
talkEntity = m_lastVictim;
|
||||
ASSIGN_TALK_ENTITY ();
|
||||
m_tempStrings += getHumanizedName (talkEntity);
|
||||
}
|
||||
pattern++;
|
||||
textStart = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (textStart != nullptr)
|
||||
{
|
||||
if (textStart != nullptr) {
|
||||
// let the bots make some mistakes...
|
||||
char tempString[160];
|
||||
strncpy (tempString, textStart, SIZEOF_CHAR (tempString));
|
||||
strncpy (tempString, textStart, cr::bufsize (tempString));
|
||||
|
||||
HumanizeChat (tempString);
|
||||
strncat (m_tempStrings, tempString, SIZEOF_CHAR (m_tempStrings));
|
||||
addChatErrors (tempString);
|
||||
m_tempStrings += tempString;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckKeywords (char *tempMessage, char *reply)
|
||||
{
|
||||
// this function checks is string contain keyword, and generates relpy to it
|
||||
bool checkForKeywords (char *message, char *reply) {
|
||||
// this function checks is string contain keyword, and generates reply to it
|
||||
|
||||
if (!yb_chat.GetBool () || IsNullString (tempMessage))
|
||||
if (!yb_chat.boolean () || isEmptyStr (message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &factory : g_replyFactory) {
|
||||
for (auto &keyword : factory.keywords) {
|
||||
|
||||
FOR_EACH_AE (g_replyFactory, i)
|
||||
{
|
||||
FOR_EACH_AE (g_replyFactory[i].keywords, j)
|
||||
{
|
||||
// check is keyword has occurred in message
|
||||
if (strstr (tempMessage, g_replyFactory[i].keywords[j].GetBuffer ()) != nullptr)
|
||||
{
|
||||
Array <String> &replies = g_replyFactory[i].usedReplies;
|
||||
if (strstr (message, keyword.chars ()) != nullptr) {
|
||||
StringArray &replies = factory.usedReplies;
|
||||
|
||||
if (replies.GetElementNumber () >= g_replyFactory[i].replies.GetElementNumber () / 2)
|
||||
replies.RemoveAll ();
|
||||
|
||||
bool replyUsed = false;
|
||||
const char *generatedReply = g_replyFactory[i].replies.GetRandomElement ();
|
||||
|
||||
// don't say this twice
|
||||
FOR_EACH_AE (replies, k)
|
||||
{
|
||||
if (strstr (replies[k].GetBuffer (), generatedReply) != nullptr)
|
||||
replyUsed = true;
|
||||
if (replies.length () >= factory.replies.length () / 2) {
|
||||
replies.clear ();
|
||||
}
|
||||
else if (!replies.empty ()) {
|
||||
bool replyUsed = false;
|
||||
const String &choosenReply = factory.replies.random ();
|
||||
|
||||
// reply not used, so use it
|
||||
if (!replyUsed)
|
||||
{
|
||||
strcpy (reply, generatedReply); // update final buffer
|
||||
replies.Push (generatedReply); //add to ignore list
|
||||
// don't say this twice
|
||||
for (auto &used : replies) {
|
||||
if (used.contains (choosenReply)) {
|
||||
replyUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
// reply not used, so use it
|
||||
if (!replyUsed) {
|
||||
strcpy (reply, choosenReply.chars ()); // update final buffer
|
||||
replies.push (choosenReply); // add to ignore list
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// didn't find a keyword? 70% of the time use some universal reply
|
||||
if (Random.Int (1, 100) < 70 && !g_chatFactory[CHAT_NOKW].IsEmpty ())
|
||||
{
|
||||
strcpy (reply, g_chatFactory[CHAT_NOKW].GetRandomElement ().GetBuffer ());
|
||||
if (rng.getInt (1, 100) < 70 && !g_chatFactory[CHAT_NOKW].empty ()) {
|
||||
strcpy (reply, g_chatFactory[CHAT_NOKW].random ().chars ());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bot::ParseChat (char *reply)
|
||||
{
|
||||
bool Bot::processChatKeywords (char *reply) {
|
||||
// this function parse chat buffer, and prepare buffer to keyword searching
|
||||
|
||||
char tempMessage[512];
|
||||
strcpy (tempMessage, m_sayTextBuffer.sayText); // copy to safe place
|
||||
size_t maxLength = cr::bufsize (tempMessage);
|
||||
|
||||
strncpy (tempMessage, m_sayTextBuffer.sayText.chars (), maxLength); // copy to safe place
|
||||
|
||||
// text to uppercase for keyword parsing
|
||||
for (int i = 0; i < static_cast <int> (strlen (tempMessage)); i++)
|
||||
tempMessage[i] = static_cast <char> (tolower (static_cast <int> (tempMessage[i])));
|
||||
|
||||
return CheckKeywords (tempMessage, reply);
|
||||
for (size_t i = 0; i < maxLength; i++) {
|
||||
tempMessage[i] = static_cast <char> (toupper (static_cast <int> (tempMessage[i])));
|
||||
}
|
||||
return checkForKeywords (tempMessage, reply);
|
||||
}
|
||||
|
||||
bool Bot::RepliesToPlayer (void)
|
||||
{
|
||||
bool Bot::isReplyingToChat (void) {
|
||||
// this function sends reply to a player
|
||||
|
||||
if (m_sayTextBuffer.entityIndex != -1 && !IsNullString (m_sayTextBuffer.sayText))
|
||||
{
|
||||
if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) {
|
||||
char text[256];
|
||||
|
||||
// check is time to chat is good
|
||||
if (m_sayTextBuffer.timeNextChat < engine.Time ())
|
||||
{
|
||||
if (Random.Int (1, 100) < m_sayTextBuffer.chatProbability + Random.Int (2, 10) && ParseChat (reinterpret_cast <char *> (&text)))
|
||||
{
|
||||
PrepareChatMessage (text);
|
||||
PushMessageQueue (GAME_MSG_SAY_CMD);
|
||||
if (m_sayTextBuffer.timeNextChat < engine.timebase ()) {
|
||||
if (rng.getInt (1, 100) < m_sayTextBuffer.chatProbability + rng.getInt (15, 35) && processChatKeywords (reinterpret_cast <char *> (&text))) {
|
||||
prepareChatMessage (text);
|
||||
pushMsgQueue (GAME_MSG_SAY_CMD);
|
||||
|
||||
|
||||
m_sayTextBuffer.entityIndex = -1;
|
||||
m_sayTextBuffer.sayText[0] = 0x0;
|
||||
m_sayTextBuffer.timeNextChat = engine.Time () + m_sayTextBuffer.chatDelay;
|
||||
m_sayTextBuffer.timeNextChat = engine.timebase () + m_sayTextBuffer.chatDelay;
|
||||
m_sayTextBuffer.sayText.clear ();
|
||||
|
||||
return true;
|
||||
}
|
||||
m_sayTextBuffer.entityIndex = -1;
|
||||
m_sayTextBuffer.sayText[0] = 0x0;
|
||||
m_sayTextBuffer.sayText.clear ();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Bot::SayText (const char *text)
|
||||
{
|
||||
void Bot::say (const char *text) {
|
||||
// this function prints saytext message to all players
|
||||
|
||||
if (IsNullString (text))
|
||||
if (isEmptyStr (text)) {
|
||||
return;
|
||||
|
||||
engine.IssueBotCommand (GetEntity (), "say \"%s\"", text);
|
||||
}
|
||||
engine.execBotCmd (ent (), "say \"%s\"", text);
|
||||
}
|
||||
|
||||
void Bot::TeamSayText (const char *text)
|
||||
{
|
||||
void Bot::sayTeam (const char *text) {
|
||||
// this function prints saytext message only for teammates
|
||||
|
||||
if (IsNullString (text))
|
||||
if (isEmptyStr (text)) {
|
||||
return;
|
||||
|
||||
engine.IssueBotCommand (GetEntity (), "say_team \"%s\"", text);
|
||||
}
|
||||
engine.execBotCmd (ent (), "say_team \"%s\"", text);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue