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:
parent
2aba34a24b
commit
d6150a8aba
22 changed files with 171 additions and 82 deletions
|
|
@ -219,4 +219,4 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
// explose global
|
// explose global
|
||||||
static auto &conf = BotConfig::get ();
|
CR_EXPOSE_GLOBAL_SINGLETON (BotConfig, conf);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,6 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto &alloc = Allocator::get ();
|
CR_EXPOSE_GLOBAL_SINGLETON (Allocator, alloc);
|
||||||
|
|
||||||
CR_NAMESPACE_END
|
CR_NAMESPACE_END
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -146,4 +146,4 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto &msgs = MessageDispatcher::get ();
|
CR_EXPOSE_GLOBAL_SINGLETON (MessageDispatcher, msgs);
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -151,4 +151,4 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
// explose global
|
// explose global
|
||||||
static auto &util = BotUtils::get ();
|
CR_EXPOSE_GLOBAL_SINGLETON (BotUtils, util);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 ());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
|
|
||||||
|
|
@ -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 ();
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue