Major refactoring (#86)

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

2031
source/control.cpp Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff