Added consistency check for graph files. Totally optional, but wiill notify user if graph file built not for loaded map.

Some minor cosmetic changes and refactoring.
Fixed #70. Better now, than never.
Implemented #121. Bots auto-kill delays.
Bumb the year.
This commit is contained in:
jeefo 2020-02-08 00:03:52 +03:00
commit d6150a8aba
22 changed files with 171 additions and 82 deletions

View file

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

View file

@ -216,4 +216,4 @@ template <typename ...Args> inline void BotControl::msg (const char *fmt, Args .
} }
// explose global // explose global
static auto &ctrl = BotControl::get (); CR_EXPOSE_GLOBAL_SINGLETON (BotControl, ctrl);

View file

@ -68,6 +68,6 @@ public:
} }
}; };
static auto &alloc = Allocator::get (); CR_EXPOSE_GLOBAL_SINGLETON (Allocator, alloc);
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -111,6 +111,10 @@ public:
#define CR_DECLARE_SCOPED_ENUM(enumName, ...) \ #define CR_DECLARE_SCOPED_ENUM(enumName, ...) \
CR_DECLARE_SCOPED_ENUM_TYPE(enumName, int32, __VA_ARGS__) \ CR_DECLARE_SCOPED_ENUM_TYPE(enumName, int32, __VA_ARGS__) \
// exposes global variable from class singleton
#define CR_EXPOSE_GLOBAL_SINGLETON(className, variable) \
static auto &variable = className::get () \
CR_NAMESPACE_END CR_NAMESPACE_END
// platform-dependant-stuff // platform-dependant-stuff

View file

@ -453,6 +453,6 @@ public:
}; };
// expose global http client // expose global http client
static auto &http = HttpClient::get (); CR_EXPOSE_GLOBAL_SINGLETON (HttpClient, http);
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -96,6 +96,6 @@ public:
}; };
// expose global instance // expose global instance
static auto &logger = SimpleLogger::get (); CR_EXPOSE_GLOBAL_SINGLETON (SimpleLogger, logger);
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -246,6 +246,6 @@ struct Platform : public Singleton <Platform> {
}; };
// expose platform singleton // expose platform singleton
static auto &plat = Platform::get (); CR_EXPOSE_GLOBAL_SINGLETON (Platform, plat);
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -60,6 +60,6 @@ public:
}; };
// expose global random generator // expose global random generator
static auto &rg = Random::get (); CR_EXPOSE_GLOBAL_SINGLETON (Random, rg);
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -905,7 +905,7 @@ public:
}; };
// expose global string pool // expose global string pool
static auto &strings = StringBuffer::get (); CR_EXPOSE_GLOBAL_SINGLETON (StringBuffer, strings);
// some limited utf8 stuff // some limited utf8 stuff
class Utf8Tools : public Singleton <Utf8Tools> { class Utf8Tools : public Singleton <Utf8Tools> {
@ -1141,6 +1141,6 @@ public:
}; };
// expose global utf8 tools // expose global utf8 tools
static auto &utf8tools = Utf8Tools::get (); CR_EXPOSE_GLOBAL_SINGLETON (Utf8Tools, utf8tools);
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -671,6 +671,6 @@ public:
}; };
// expose globals // expose globals
static auto &game = Game::get (); CR_EXPOSE_GLOBAL_SINGLETON (Game, game);
static auto &illum = LightMeasure::get (); CR_EXPOSE_GLOBAL_SINGLETON (LightMeasure, illum);
static auto &ents = DynamicEntityLink::get (); CR_EXPOSE_GLOBAL_SINGLETON (DynamicEntityLink, ents);

View file

@ -71,7 +71,7 @@ CR_DECLARE_SCOPED_ENUM (StorageOption,
Graph = cr::bit (3), // this is a node graph data Graph = cr::bit (3), // this is a node graph data
Official = cr::bit (4), // this is additional flag for graph indicates graph are official Official = cr::bit (4), // this is additional flag for graph indicates graph are official
Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad
Author = cr::bit (6) // this is additional flag indicates that there's author info Exten = cr::bit (6) // this is additional flag indicates that there's extension info
) )
// storage header versions // storage header versions
@ -112,6 +112,12 @@ struct StorageHeader {
int32 uncompressed; int32 uncompressed;
}; };
// extension header for graph information
struct ExtenHeader {
char author[32];
int32 mapSize;
};
// general waypoint header information structure // general waypoint header information structure
struct PODGraphHeader { struct PODGraphHeader {
char header[8]; char header[8];
@ -313,8 +319,8 @@ public:
bool saveGraphData (); bool saveGraphData ();
bool loadGraphData (); bool loadGraphData ();
template <typename U> bool saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const SmallArray <U> &data, uint8 *blob); template <typename U> bool saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const SmallArray <U> &data, ExtenHeader *exten);
template <typename U> bool loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, SmallArray <U> &data, uint8 *blob, int32 *outOptions); template <typename U> bool loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, SmallArray <U> &data, ExtenHeader *exten, int32 *outOptions);
void saveOldFormat (); void saveOldFormat ();
void initGraph (); void initGraph ();
@ -431,4 +437,4 @@ public:
}; };
// explose global // explose global
static auto &graph = BotGraph::get (); CR_EXPOSE_GLOBAL_SINGLETON (BotGraph, graph);

View file

@ -29,6 +29,7 @@ private:
float m_timeRoundEnd; float m_timeRoundEnd;
float m_timeRoundMid; float m_timeRoundMid;
float m_autoKillCheckTime; // time to kill all the bots ?
float m_maintainTime; // time to maintain bot creation float m_maintainTime; // time to maintain bot creation
float m_quotaMaintainTime; // time to maintain bot quota float m_quotaMaintainTime; // time to maintain bot quota
float m_grenadeUpdateTime; // time to update active grenades float m_grenadeUpdateTime; // time to update active grenades
@ -47,7 +48,7 @@ private:
bool m_economicsGood[kGameTeamNum]; // is team able to buy anything bool m_economicsGood[kGameTeamNum]; // is team able to buy anything
bool m_bombPlanted; bool m_bombPlanted;
bool m_botsCanPause; bool m_botsCanPause;
bool m_roundEnded; bool m_roundOver;
Array <edict_t *> m_activeGrenades; // holds currently active grenades on the map Array <edict_t *> m_activeGrenades; // holds currently active grenades on the map
Array <edict_t *> m_intrestingEntities; // holds currently intresting entities on the map Array <edict_t *> m_intrestingEntities; // holds currently intresting entities on the map
@ -93,6 +94,7 @@ public:
void kickFromTeam (Team team, bool removeAll = false); void kickFromTeam (Team team, bool removeAll = false);
void killAllBots (int team = -1); void killAllBots (int team = -1);
void maintainQuota (); void maintainQuota ();
void maintainAutoKill ();
void initQuota (); void initQuota ();
void initRound (); void initRound ();
void decrementQuota (int by = 1); void decrementQuota (int by = 1);
@ -172,11 +174,7 @@ public:
} }
bool isRoundOver () const { bool isRoundOver () const {
return m_roundEnded; return m_roundOver;
}
void setRoundOver (const bool over) {
m_roundEnded = over;
} }
bool canPause () const { bool canPause () const {
@ -265,4 +263,4 @@ public:
}; };
// explose global // explose global
static auto &bots = BotManager::get (); CR_EXPOSE_GLOBAL_SINGLETON (BotManager, bots);

View file

@ -146,4 +146,4 @@ private:
} }
}; };
static auto &msgs = MessageDispatcher::get (); CR_EXPOSE_GLOBAL_SINGLETON (MessageDispatcher, msgs);

View file

@ -16,7 +16,7 @@
#define PRODUCT_URL "https://yapb.ru/" #define PRODUCT_URL "https://yapb.ru/"
#define PRODUCT_EMAIL "yapb@entix.io" #define PRODUCT_EMAIL "yapb@entix.io"
#define PRODUCT_LOGTAG "YAPB" #define PRODUCT_LOGTAG "YAPB"
#define PRODUCT_END_YEAR "2019" #define PRODUCT_END_YEAR "2020"
#define PRODUCT_DESCRIPTION PRODUCT_NAME " v" PRODUCT_VERSION " - The Counter-Strike Bot (" PRODUCT_COMMENTS ")" #define PRODUCT_DESCRIPTION PRODUCT_NAME " v" PRODUCT_VERSION " - The Counter-Strike Bot (" PRODUCT_COMMENTS ")"
#define PRODUCT_COPYRIGHT "Copyright © 2004-" PRODUCT_END_YEAR ", by " PRODUCT_AUTHOR #define PRODUCT_COPYRIGHT "Copyright © 2004-" PRODUCT_END_YEAR ", by " PRODUCT_AUTHOR
#define PRODUCT_LEGAL "Half-Life, Counter-Strike, Counter-Strike: Condition Zero, Steam, Valve is a trademark of Valve Corporation" #define PRODUCT_LEGAL "Half-Life, Counter-Strike, Counter-Strike: Condition Zero, Steam, Valve is a trademark of Valve Corporation"
@ -25,7 +25,7 @@
#define PRODUCT_GIT_HASH "unspecified_hash" #define PRODUCT_GIT_HASH "unspecified_hash"
#define PRODUCT_GIT_COMMIT_AUTHOR "unspecified_author" #define PRODUCT_GIT_COMMIT_AUTHOR "unspecified_author"
#define PRODUCT_GIT_COMMIT_ID 0000 #define PRODUCT_GIT_COMMIT_ID 0000
#define PRODUCT_VERSION_DWORD_INTERNAL 2, 92 #define PRODUCT_VERSION_DWORD_INTERNAL 2,92
#define PRODUCT_VERSION_DWORD PRODUCT_VERSION_DWORD_INTERNAL, PRODUCT_GIT_COMMIT_ID #define PRODUCT_VERSION_DWORD PRODUCT_VERSION_DWORD_INTERNAL, PRODUCT_GIT_COMMIT_ID
#define PRODUCT_SUPPORT_VERSION "Beta 6.6 - Condition Zero" #define PRODUCT_SUPPORT_VERSION "Beta 6.6 - Condition Zero"
#define PRODUCT_COMMENTS "http://github.com/jeefo/yapb/" #define PRODUCT_COMMENTS "http://github.com/jeefo/yapb/"

View file

@ -151,4 +151,4 @@ public:
}; };
// explose global // explose global
static auto &util = BotUtils::get (); CR_EXPOSE_GLOBAL_SINGLETON (BotUtils, util);

View file

@ -1,10 +1,9 @@
// //
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright (c) YaPB Development Team. // Copyright (c) Yet Another POD-Bot Contributors <yapb@entix.io>.
// //
// This software is licensed under the BSD-style license. // This software is licensed under the MIT license.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit: // Additional exceptions apply. For full license details, see LICENSE.txt
// https://yapb.ru/license
// //
#include <winver.h> #include <winver.h>

View file

@ -484,7 +484,7 @@ void Bot::updatePickups () {
} }
auto &intresting = bots.searchIntrestingEntities (); auto &intresting = bots.searchIntrestingEntities ();
const float radius = cr::square (320.0f); const float radius = cr::square (399.0f);
if (!game.isNullEntity (m_pickupItem)) { if (!game.isNullEntity (m_pickupItem)) {
bool itemExists = false; bool itemExists = false;
@ -2176,7 +2176,7 @@ bool Bot::reactOnEnemy () {
auto lineDist = (m_enemy->v.origin - pev->origin).length (); auto lineDist = (m_enemy->v.origin - pev->origin).length ();
auto pathDist = static_cast <float> (graph.getPathDist (ownIndex, enemyIndex)); auto pathDist = static_cast <float> (graph.getPathDist (ownIndex, enemyIndex));
if (pathDist - lineDist > 112.0f) { if (pathDist - lineDist > 112.0f || isOnLadder ()) {
m_isEnemyReachable = false; m_isEnemyReachable = false;
} }
else { else {
@ -3444,11 +3444,6 @@ void Bot::attackEnemy_ () {
if (!game.isNullEntity (m_enemy)) { if (!game.isNullEntity (m_enemy)) {
ignoreCollision (); ignoreCollision ();
if (isOnLadder ()) {
pev->button |= IN_JUMP;
clearSearchNodes ();
}
attackMovement (); attackMovement ();
if (m_currentWeapon == Weapon::Knife && !m_lastEnemyOrigin.empty ()) { if (m_currentWeapon == Weapon::Knife && !m_lastEnemyOrigin.empty ()) {
@ -3796,7 +3791,17 @@ void Bot::defuseBomb_ () {
// exception: bomb has been defused // exception: bomb has been defused
if (bombPos.empty () || !pickupExists) { if (bombPos.empty () || !pickupExists) {
defuseError = true;
// fix for stupid behaviour of CT's when bot is defused
for (const auto &bot : bots) {
if (bot->m_team == m_team && bot->m_notKilled) {
auto defendPoint = graph.getFarest (bot->pev->origin);
startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (30.0f, 60.0f), true); // push camp task on to stack
startTask (Task::MoveToPosition, TaskPri::MoveToPosition, defendPoint, game.time () + rg.float_ (3.0f, 6.0f), true); // push move command
}
}
graph.setBombOrigin (true);
if (m_numFriendsLeft != 0 && rg.chance (50)) { if (m_numFriendsLeft != 0 && rg.chance (50)) {
if (timeToBlowUp <= 3.0) { if (timeToBlowUp <= 3.0) {
@ -3811,6 +3816,7 @@ void Bot::defuseBomb_ () {
pushRadioMessage (Radio::SectorClear); pushRadioMessage (Radio::SectorClear);
} }
} }
return;
} }
else if (defuseRemainingTime > timeToBlowUp) { else if (defuseRemainingTime > timeToBlowUp) {
defuseError = true; defuseError = true;
@ -3833,17 +3839,16 @@ void Bot::defuseBomb_ () {
// one of exceptions is thrown. finish task. // one of exceptions is thrown. finish task.
if (defuseError) { if (defuseError) {
m_checkTerrain = true;
m_moveToGoal = true;
m_destOrigin = nullptr;
m_entity = nullptr; m_entity = nullptr;
m_pickupItem = nullptr; m_pickupItem = nullptr;
m_pickupType = Pickup::None; m_pickupType = Pickup::None;
selectBestWeapon (); selectBestWeapon ();
resetCollision ();
completeTask (); completeTask ();
return; return;
} }

View file

@ -917,7 +917,7 @@ void Bot::fireWeapons () {
if (weapons & cr::bit (id)) { if (weapons & cr::bit (id)) {
const auto &prop = conf.getWeaponProp (id); const auto &prop = conf.getWeaponProp (id);
if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) { if (prop.ammo1 != -1 && prop.ammo1 < kMaxWeapons && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) {
// available ammo found, reload weapon // available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) { if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) {
@ -945,7 +945,7 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
auto &info = conf.getWeapons (); auto &info = conf.getWeapons ();
if (m_difficulty < 2) { if (m_difficulty < 2 || !hasSecondaryWeapon ()) {
return false; return false;
} }
int wid = info[weaponIndex].id; int wid = info[weaponIndex].id;
@ -1415,7 +1415,7 @@ void Bot::selectBestWeapon () {
const auto &prop = conf.getWeaponProp (id); const auto &prop = conf.getWeaponProp (id);
// is no ammo required for this weapon OR enough ammo available to fire // is no ammo required for this weapon OR enough ammo available to fire
if (prop.ammo1 < 0 || (prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo)) { if (prop.ammo1 < 0 || (prop.ammo1 < kMaxWeapons && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo)) {
ammoLeft = true; ammoLeft = true;
} }
@ -1607,7 +1607,7 @@ void Bot::checkReload () {
} }
const auto &prop = conf.getWeaponProp (weaponIndex); const auto &prop = conf.getWeaponProp (weaponIndex);
if (m_ammoInClip[weaponIndex] < conf.findWeaponById (weaponIndex).maxClip * 0.8f && prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] > 0) { if (m_ammoInClip[weaponIndex] < conf.findWeaponById (weaponIndex).maxClip * 0.8f && prop.ammo1 != -1 && prop.ammo1 < kMaxWeapons && m_ammo[prop.ammo1] > 0) {
if (m_currentWeapon != weaponIndex) { if (m_currentWeapon != weaponIndex) {
selectWeaponByName (prop.classname.chars ()); selectWeaponByName (prop.classname.chars ());
} }

View file

@ -36,8 +36,6 @@ void Game::precache () {
} }
m_precached = true; m_precached = true;
m_drawModels[DrawLine::Simple] = m_engineWrap.precacheModel ("sprites/laserbeam.spr"); m_drawModels[DrawLine::Simple] = m_engineWrap.precacheModel ("sprites/laserbeam.spr");
m_drawModels[DrawLine::Arrow] = m_engineWrap.precacheModel ("sprites/arrow1.spr"); m_drawModels[DrawLine::Arrow] = m_engineWrap.precacheModel ("sprites/arrow1.spr");
@ -842,6 +840,9 @@ void Game::slowFrame () {
// update bot difficulties to newly selected from cvar // update bot difficulties to newly selected from cvar
bots.updateBotDifficulties (); bots.updateBotDifficulties ();
// check if we're need to autokill bots
bots.maintainAutoKill ();
// update client pings // update client pings
util.calculatePings (); util.calculatePings ();

View file

@ -1473,7 +1473,7 @@ bool BotGraph::convertOldFormat () {
return true; return true;
} }
template <typename U> bool BotGraph::saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const SmallArray <U> &data, uint8 *blob) { template <typename U> bool BotGraph::saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const SmallArray <U> &data, ExtenHeader *exten) {
bool isGraph = (ext == "graph"); bool isGraph = (ext == "graph");
String filename; String filename;
@ -1521,9 +1521,9 @@ template <typename U> bool BotGraph::saveStorage (const String &ext, const Strin
file.write (&hdr, sizeof (StorageHeader)); file.write (&hdr, sizeof (StorageHeader));
file.write (compressed.data (), sizeof (uint8), compressedLength); file.write (compressed.data (), sizeof (uint8), compressedLength);
// add creator // add extension
if ((options & StorageOption::Author) && blob != nullptr) { if ((options & StorageOption::Exten) && exten != nullptr) {
file.write (blob, sizeof (uint8), 64); file.write (exten, sizeof (ExtenHeader));
} }
game.print ("Successfully saved Bots %s data.", name.chars ()); game.print ("Successfully saved Bots %s data.", name.chars ());
} }
@ -1537,7 +1537,7 @@ template <typename U> bool BotGraph::saveStorage (const String &ext, const Strin
return true; return true;
} }
template <typename U> bool BotGraph::loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, SmallArray <U> &data, uint8 *blob, int32 *outOptions) { template <typename U> bool BotGraph::loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, SmallArray <U> &data, ExtenHeader *exten, int32 *outOptions) {
String filename; String filename;
filename.assignf ("%s.%s", game.getMapName (), ext.chars ()).lowercase (); filename.assignf ("%s.%s", game.getMapName (), ext.chars ()).lowercase ();
@ -1614,11 +1614,11 @@ template <typename U> bool BotGraph::loadStorage (const String &ext, const Strin
} }
if (download ()) { if (download ()) {
return loadStorage <U> (ext, name, options, version, data, blob, outOptions); return loadStorage <U> (ext, name, options, version, data, exten, outOptions);
} }
if (convertOldFormat ()) { if (convertOldFormat ()) {
return loadStorage <U> (ext, name, options, version, data, blob, outOptions); return loadStorage <U> (ext, name, options, version, data, exten, outOptions);
} }
return false; return false;
}; };
@ -1690,8 +1690,8 @@ template <typename U> bool BotGraph::loadStorage (const String &ext, const Strin
} }
// author of graph.. save // author of graph.. save
if ((hdr.options & StorageOption::Author) && blob != nullptr) { if ((hdr.options & StorageOption::Exten) && exten != nullptr) {
file.read (blob, sizeof (uint8), 64); file.read (exten, sizeof (ExtenHeader));
} }
game.print ("Successfully loaded Bots %s data (%d/%.2fMB).", name.chars (), data.length (), static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); game.print ("Successfully loaded Bots %s data (%d/%.2fMB).", name.chars (), data.length (), static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f);
file.close (); file.close ();
@ -1706,13 +1706,13 @@ template <typename U> bool BotGraph::loadStorage (const String &ext, const Strin
} }
bool BotGraph::loadGraphData () { bool BotGraph::loadGraphData () {
uint8 blob[64]; ExtenHeader exten {};
int32 outOptions = 0; int32 outOptions = 0;
m_paths.clear (); m_paths.clear ();
// check if loaded // check if loaded
bool dataLoaded = loadStorage <Path> ("graph", "Graph", StorageOption::Graph, StorageVersion::Graph, m_paths, reinterpret_cast <uint8 *> (blob), &outOptions); bool dataLoaded = loadStorage <Path> ("graph", "Graph", StorageOption::Graph, StorageVersion::Graph, m_paths, &exten, &outOptions);
if (dataLoaded) { if (dataLoaded) {
initGraph (); initGraph ();
@ -1723,17 +1723,24 @@ bool BotGraph::loadGraphData () {
addToBucket (path.origin, path.number); addToBucket (path.origin, path.number);
} }
if ((outOptions & StorageOption::Official) || memcmp (blob, "official", 8) == 0) { if ((outOptions & StorageOption::Official) || strncmp (exten.author, "official", 8) == 0 || strlen (exten.author) < 2) {
m_tempStrings.assign ("Using Official Graph File"); m_tempStrings.assign ("Using Official Navigation Graph");
} }
else { else {
m_tempStrings.assignf ("Using Graph File By: %s", blob); m_tempStrings.assignf ("Navigation Graph Authord By: %s", exten.author);
} }
initNodesTypes (); initNodesTypes ();
loadPathMatrix (); loadPathMatrix ();
loadVisibility (); loadVisibility ();
loadPractice (); loadPractice ();
if (exten.mapSize > 0) {
int mapSize = engfuncs.pfnGetFileSize (strings.format ("maps/%s.bsp", game.getMapName ()));
if (mapSize != exten.mapSize) {
game.print ("Warning: Graph data is probably not for this map. Please check bots behaviour.");
}
}
extern ConVar yb_debug_goal; extern ConVar yb_debug_goal;
yb_debug_goal.set (kInvalidNodeIndex); yb_debug_goal.set (kInvalidNodeIndex);
@ -1743,7 +1750,7 @@ bool BotGraph::loadGraphData () {
} }
bool BotGraph::saveGraphData () { bool BotGraph::saveGraphData () {
auto options = StorageOption::Graph | StorageOption::Author; auto options = StorageOption::Graph | StorageOption::Exten;
String author; String author;
if (game.isNullEntity (m_editor) && !m_tempStrings.empty ()) { if (game.isNullEntity (m_editor) && !m_tempStrings.empty ()) {
@ -1757,13 +1764,17 @@ bool BotGraph::saveGraphData () {
else { else {
author = "YAPB"; author = "YAPB";
} }
author.resize (64);
// mark as official // mark as official
if (author.startsWith ("YAPB")) { if (author.startsWith ("YAPB")) {
options |= StorageOption::Official; options |= StorageOption::Official;
} }
return saveStorage <Path> ("graph", "Graph", static_cast <StorageOption> (options), StorageVersion::Graph, m_paths, reinterpret_cast <uint8 *> (author.begin ()));
ExtenHeader exten {};
strings.copy (exten.author, author.chars (), cr::bufsize (exten.author));
exten.mapSize = engfuncs.pfnGetFileSize (strings.format ("maps/%s.bsp", game.getMapName ()));
return saveStorage <Path> ("graph", "Graph", static_cast <StorageOption> (options), StorageVersion::Graph, m_paths, &exten);
} }
void BotGraph::saveOldFormat () { void BotGraph::saveOldFormat () {
@ -2673,16 +2684,18 @@ void BotGraph::eraseFromDisk () {
// this function removes graph file from the hard disk // this function removes graph file from the hard disk
StringArray forErase; StringArray forErase;
const char *map = game.getMapName ();
auto map = game.getMapName ();
auto data = getDataDirectory ();
bots.kickEveryone (true); bots.kickEveryone (true);
// if we're delete graph, delete all corresponding to it files // if we're delete graph, delete all corresponding to it files
forErase.push (strings.format ("%s%s.pwf", getDataDirectory (), map)); // graph itself forErase.push (strings.format ("%s%s.pwf", data, map)); // graph itself
forErase.push (strings.format ("%slearned/%s.exp", getDataDirectory (), map)); // corresponding to practice forErase.push (strings.format ("%slearned/%s.exp", data, map)); // corresponding to practice
forErase.push (strings.format ("%slearned/%s.vis", getDataDirectory (), map)); // corresponding to vistable forErase.push (strings.format ("%slearned/%s.vis", data, map)); // corresponding to vistable
forErase.push (strings.format ("%slearned/%s.pmx", getDataDirectory (), map)); // corresponding to matrix forErase.push (strings.format ("%slearned/%s.pmx", data, map)); // corresponding to matrix
forErase.push (strings.format ("%sgraph/%s.graph", getDataDirectory (), map)); // new format graph forErase.push (strings.format ("%sgraph/%s.graph", data, map)); // new format graph
for (const auto &item : forErase) { for (const auto &item : forErase) {
if (File::exists (item)) { if (File::exists (item)) {

View file

@ -15,6 +15,7 @@ ConVar yb_quota ("yb_quota", "0", "Specifies the number bots to be added to the
ConVar yb_quota_mode ("yb_quota_mode", "normal", "Specifies the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is yb_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is yb_quota_match.", false); ConVar yb_quota_mode ("yb_quota_mode", "normal", "Specifies the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is yb_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is yb_quota_match.", false);
ConVar yb_quota_match ("yb_quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast <float> (kGameMaxPlayers)); ConVar yb_quota_match ("yb_quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar yb_think_fps ("yb_think_fps", "30.0", "Specifies hou many times per second bot code will run.", true, 30.0f, 90.0f); ConVar yb_think_fps ("yb_think_fps", "30.0", "Specifies hou many times per second bot code will run.", true, 30.0f, 90.0f);
ConVar yb_autokill_delay ("yb_autokill_delay", "0.0", "Specifies amount of time in seconds when bots will be killed if no humans left alive.", true, 0.0f, 90.0f);
ConVar yb_join_after_player ("yb_join_after_player", "0", "Sepcifies whether bots should join server, only when at least one human player in game."); ConVar yb_join_after_player ("yb_join_after_player", "0", "Sepcifies whether bots should join server, only when at least one human player in game.");
ConVar yb_join_team ("yb_join_team", "any", "Forces all bots to join team specified here.", false); ConVar yb_join_team ("yb_join_team", "any", "Forces all bots to join team specified here.", false);
@ -45,9 +46,11 @@ BotManager::BotManager () {
m_timeRoundMid = 0.0f; m_timeRoundMid = 0.0f;
m_timeRoundEnd = 0.0f; m_timeRoundEnd = 0.0f;
m_autoKillCheckTime = 0.0f;
m_bombPlanted = false; m_bombPlanted = false;
m_botsCanPause = false; m_botsCanPause = false;
m_roundEnded = false; m_roundOver = false;
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
@ -67,7 +70,7 @@ BotManager::BotManager () {
} }
void BotManager::createKillerEntity () { void BotManager::createKillerEntity () {
// this function creates single trigger_hurt for using in Bot::Kill, to reduce lags, when killing all the bots // this function creates single trigger_hurt for using in Bot::kill, to reduce lags, when killing all the bots
m_killerEntity = engfuncs.pfnCreateNamedEntity (MAKE_STRING ("trigger_hurt")); m_killerEntity = engfuncs.pfnCreateNamedEntity (MAKE_STRING ("trigger_hurt"));
@ -410,6 +413,52 @@ void BotManager::maintainQuota () {
m_quotaMaintainTime = game.time () + 0.40f; m_quotaMaintainTime = game.time () + 0.40f;
} }
void BotManager::maintainAutoKill () {
const float killDelay = yb_autokill_delay.float_ ();
if (killDelay < 1.0f || m_roundOver) {
return;
}
// check if we're reached the delay, so kill out bots
if (!cr::fzero (m_autoKillCheckTime) && m_autoKillCheckTime < game.time ()) {
killAllBots ();
m_autoKillCheckTime = 0.0f;
return;
}
int aliveBots = 0;
// do not interrupt bomb-defuse scenario
if (game.mapIs (MapFlags::Demolition) && isBombPlanted ()) {
return;
}
int totalHumans = getHumansCount (true); // we're ignore spectators intentionally
// if we're have no humans in teams do not bother to proceed
if (!totalHumans) {
return;
}
for (const auto &bot : m_bots) {
if (bot->m_notKilled) {
++aliveBots;
// do not interrupt assassination scenario, if vip is a bot
if (game.is (MapFlags::Assassination) && util.isPlayerVIP (bot->ent ())) {
return;
}
}
}
int aliveHumans = getAliveHumansCount ();
// check if we're have no alive players and some alive bots, and start autokill timer
if (!aliveHumans && aliveBots > 0 && cr::fzero (m_autoKillCheckTime)) {
m_autoKillCheckTime = game.time () + killDelay;
}
}
void BotManager::reset () { void BotManager::reset () {
m_maintainTime = 0.0f; m_maintainTime = 0.0f;
m_quotaMaintainTime = 0.0f; m_quotaMaintainTime = 0.0f;
@ -621,6 +670,7 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) {
void BotManager::setLastWinner (int winner) { void BotManager::setLastWinner (int winner) {
m_lastWinner = winner; m_lastWinner = winner;
m_roundOver = true;
if (yb_radio_mode.int_ () != 2) { if (yb_radio_mode.int_ () != 2) {
return; return;
@ -702,7 +752,7 @@ float BotManager::getConnectionTime (int botId) {
if (bot->index () == botId) { if (bot->index () == botId) {
auto current = plat.seconds (); auto current = plat.seconds ();
if (current - bot->m_joinServerTime > bot->m_playServerTime || current - bot->m_joinServerTime <= 0) { if (current - bot->m_joinServerTime > bot->m_playServerTime || current - bot->m_joinServerTime <= 0.0f) {
bot->m_playServerTime = 60.0f * rg.float_ (30.0f, 240.0f); bot->m_playServerTime = 60.0f * rg.float_ (30.0f, 240.0f);
bot->m_joinServerTime = current - bot->m_playServerTime * rg.float_ (0.2f, 0.8f); bot->m_joinServerTime = current - bot->m_playServerTime * rg.float_ (0.2f, 0.8f);
} }
@ -950,7 +1000,7 @@ int BotManager::getAliveHumansCount () {
int count = 0; int count = 0;
for (const auto &client : util.getClients ()) { for (const auto &client : util.getClients ()) {
if ((client.flags & (ClientFlags::Used | ClientFlags::Alive)) && bots[client.ent] == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { if ((client.flags & ClientFlags::Alive) && bots[client.ent] == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) {
++count; ++count;
} }
} }
@ -1288,6 +1338,10 @@ void Bot::kick () {
void Bot::updateTeamJoin () { void Bot::updateTeamJoin () {
// this function handles the selection of teams & class // this function handles the selection of teams & class
if (!m_notStarted) {
return;
}
// cs prior beta 7.0 uses hud-based motd, so press fire once // cs prior beta 7.0 uses hud-based motd, so press fire once
if (game.is (GameFlags::Legacy)) { if (game.is (GameFlags::Legacy)) {
pev->button |= IN_ATTACK; pev->button |= IN_ATTACK;
@ -1302,8 +1356,8 @@ void Bot::updateTeamJoin () {
} }
// if bot was unable to join team, and no menus popups, check for stacked team // if bot was unable to join team, and no menus popups, check for stacked team
if (m_startAction == BotMsg::None && ++m_retryJoin > 3) { if (m_startAction == BotMsg::None) {
if (bots.isTeamStacked (m_wantedTeam - 1)) { if (++m_retryJoin > 3 && bots.isTeamStacked (m_wantedTeam - 1)) {
m_retryJoin = 0; m_retryJoin = 0;
ctrl.msg ("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round)."); ctrl.msg ("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round).");
@ -1417,6 +1471,10 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
void BotManager::notifyBombDefuse () { void BotManager::notifyBombDefuse () {
// notify all terrorists that CT is starting bomb defusing // notify all terrorists that CT is starting bomb defusing
if (!isBombPlanted ()) {
return;
}
for (const auto &bot : bots) { for (const auto &bot : bots) {
if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) { if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) {
bot->clearSearchNodes (); bot->clearSearchNodes ();
@ -1584,7 +1642,7 @@ void BotManager::selectLeaders (int team, bool reset) {
void BotManager::initRound () { void BotManager::initRound () {
// this is called at the start of each round // this is called at the start of each round
m_roundEnded = false; m_roundOver = false;
// check team economics // check team economics
for (int team = 0; team < kGameTeamNum; ++team) { for (int team = 0; team < kGameTeamNum; ++team) {
@ -1611,6 +1669,7 @@ void BotManager::initRound () {
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
m_timeBombPlanted = 0.0f; m_timeBombPlanted = 0.0f;
m_plantSearchUpdateTime = 0.0f; m_plantSearchUpdateTime = 0.0f;
m_autoKillCheckTime = 0.0f;
m_botsCanPause = false; m_botsCanPause = false;
resetFilters (); resetFilters ();

View file

@ -24,7 +24,6 @@ void MessageDispatcher::netMsgTextMsg () {
return; return;
} }
// reset bomb position for all the bots // reset bomb position for all the bots
const auto resetBombPosition = [] () -> void { const auto resetBombPosition = [] () -> void {
if (game.mapIs (MapFlags::Demolition)) { if (game.mapIs (MapFlags::Demolition)) {
@ -115,7 +114,12 @@ void MessageDispatcher::netMsgShowMenu () {
if (m_args.length () < min || !m_bot) { if (m_args.length () < min || !m_bot) {
return; return;
} }
m_bot->m_startAction = m_showMenuCache[m_args[menu].chars_]; auto cached = m_showMenuCache[m_args[menu].chars_];
// only assign if non-zero
if (cached > 0) {
m_bot->m_startAction = cached;
}
} }
void MessageDispatcher::netMsgWeaponList () { void MessageDispatcher::netMsgWeaponList () {