hearing: randomize listen position for heard sounds

graph: restore cvar variables values to initial values when disabling graph editor
cfg: update primary config with actual cvars
vision: remove unnecessary angles clamp
combat: allow to disable engine potential visibility checks
combat: do not aim to head on long distances with AWP, shotguns or with high recoil
bot: set the minimum think fps to 30.0
This commit is contained in:
jeefo 2024-04-11 16:26:12 +03:00
commit cc01693de9
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
11 changed files with 110 additions and 46 deletions

View file

@ -235,6 +235,13 @@ yb_pickup_best "1"
// //
yb_ignore_objectives "0" yb_ignore_objectives "0"
//
// Affect bot's vision by smoke clouds.
// ---
// Default: "2", Min: "0", Max: "2"
//
yb_smoke_grenade_checks "2"
// //
// Enables or disables bots chat functionality. // Enables or disables bots chat functionality.
// --- // ---
@ -252,9 +259,9 @@ yb_chat_percent "30"
// //
// Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them. // Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.
// --- // ---
// Default: "2", Min: "0", Max: "3" // Default: "1", Min: "0", Max: "3"
// //
yb_shoots_thru_walls "2" yb_shoots_thru_walls "1"
// //
// Enables or disables searching world for enemies. // Enables or disables searching world for enemies.
@ -284,6 +291,13 @@ yb_check_enemy_invincibility "0"
// //
yb_stab_close_enemies "1" yb_stab_close_enemies "1"
//
// Use engine to check potential visibility of a enemy.
// ---
// Default: "0", Min: "0", Max: "1"
//
yb_use_engine_pvs_check "0"
// //
// Binds specified key for opening bots menu. // Binds specified key for opening bots menu.
// --- // ---
@ -352,6 +366,13 @@ yb_threadpool_workers "-1"
// //
yb_grenadier_mode "0" yb_grenadier_mode "0"
//
// Make bots ignore enemies for a specified here time in seconds on new round. Useful for Zombie Plague mods.
// ---
// Default: "0", Min: "0", Max: "540"
//
yb_ignore_enemies_after_spawn_time "0"
// //
// Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format. // Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format.
// --- // ---
@ -442,9 +463,9 @@ yb_quota_match "0"
// //
// Specifies how many times per second bot code will run. // Specifies how many times per second bot code will run.
// --- // ---
// Default: "26.0", Min: "24.0", Max: "90.0" // Default: "30.0", Min: "24.0", Max: "90.0"
// //
yb_think_fps "26.0" yb_think_fps "30.0"
// //
// Specifies amount of time in seconds when bots will be killed if no humans left alive. // Specifies amount of time in seconds when bots will be killed if no humans left alive.

View file

@ -427,8 +427,8 @@ namespace TaskPri {
constexpr auto kInfiniteDistance = 9999999.0f; constexpr auto kInfiniteDistance = 9999999.0f;
constexpr auto kInvalidLightLevel = kInfiniteDistance; constexpr auto kInvalidLightLevel = kInfiniteDistance;
constexpr auto kGrenadeCheckTime = 0.6f; constexpr auto kGrenadeCheckTime = 0.6f;
constexpr auto kSprayDistance = 260.0f; constexpr auto kSprayDistance = 360.0f;
constexpr auto kDoubleSprayDistance = kSprayDistance * 2; constexpr auto kSprayDistanceX2 = kSprayDistance * 2;
constexpr auto kMaxChatterRepeatInterval = 99.0f; constexpr auto kMaxChatterRepeatInterval = 99.0f;
constexpr auto kViewFrameUpdate = 1.0f / 30.0f; constexpr auto kViewFrameUpdate = 1.0f / 30.0f;
constexpr auto kGrenadeDamageRadius = 385.0f; constexpr auto kGrenadeDamageRadius = 385.0f;

View file

@ -63,6 +63,13 @@ public:
{ } { }
}; };
// save old values of changed cvars to revert them back when editing turned off
struct GraphSaveVarValue {
float timelimit {};
float freezetime {};
float roundtime {};
} m_graphSaveVarValues;
private: private:
StringArray m_args {}; StringArray m_args {};
Array <BotCmd> m_cmds {}; Array <BotCmd> m_cmds {};

View file

@ -790,6 +790,11 @@ private:
return m_weaponType == WeaponType::Sniper; return m_weaponType == WeaponType::Sniper;
} }
// returns true if bot is using a sniper rifle (awp)
bool usesSniperAWP () const {
return m_currentWeapon == Weapon::AWP;
}
// returns true if bot is using a rifle // returns true if bot is using a rifle
bool usesRifle () const { bool usesRifle () const {
return usesZoomableRifle () || m_weaponType == WeaponType::Rifle; return usesZoomableRifle () || m_weaponType == WeaponType::Rifle;
@ -834,6 +839,11 @@ private:
bool usesKnife () const { bool usesKnife () const {
return m_weaponType == WeaponType::Melee; return m_weaponType == WeaponType::Melee;
} }
// checks if weapon recoil is high
bool isRecoilHigh () const {
return pev->punchangle.x < -1.45f;
}
}; };
#include "config.h" #include "config.h"

View file

@ -1711,6 +1711,7 @@ void Bot::overrideConditions () {
m_isReloading = false; m_isReloading = false;
} }
} }
if (m_seeEnemyTime + 2.5f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) { if (m_seeEnemyTime + 2.5f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) {
m_moveSpeed = m_fearLevel > m_agressionLevel ? 0.0f : getShiftSpeed (); m_moveSpeed = m_fearLevel > m_agressionLevel ? 0.0f : getShiftSpeed ();
m_navTimeset = game.time (); m_navTimeset = game.time ();
@ -1785,10 +1786,14 @@ void Bot::refreshEnemyPredict () {
} }
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f
&& distanceToLastEnemySq < cr::sqrf (256.0f) && distanceToLastEnemySq < cr::sqrf (256.0f)
&& m_shootTime + 2.5f > game.time (); && m_shootTime + 1.5f > game.time ();
if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
m_aimFlags |= AimFlags::LastEnemy; const auto weaponPenetratePower = conf.findWeaponById (m_currentWeapon).penetratePower;
if (isPenetrableObstacle3 (m_lastEnemyOrigin, weaponPenetratePower)) {
m_aimFlags |= AimFlags::LastEnemy;
}
} }
} }
@ -1801,7 +1806,7 @@ void Bot::setLastVictim (edict_t *ent) {
m_lastVictim = ent; m_lastVictim = ent;
m_lastVictimOrigin = ent->v.origin + ent->v.view_ofs; m_lastVictimOrigin = ent->v.origin + ent->v.view_ofs;
m_forgetLastVictimTimer.start (rg.get (0.5f, 0.8f)); m_forgetLastVictimTimer.start (rg.get (1.0f, 2.0f));
} }
void Bot::setConditions () { void Bot::setConditions () {
@ -3869,7 +3874,7 @@ void Bot::updateHearing () {
} }
// did the bot hear someone ? // did the bot hear someone ?
if (hearedEnemy != nullptr && util.isPlayer (hearedEnemy)) { if (util.isPlayer (hearedEnemy)) {
// change to best weapon if heard something // change to best weapon if heard something
if (m_shootTime < game.time () - 5.0f if (m_shootTime < game.time () - 5.0f
&& isOnFloor () && isOnFloor ()
@ -3889,10 +3894,20 @@ void Bot::updateHearing () {
pushChatterMessage (Chatter::HeardTheEnemy); pushChatterMessage (Chatter::HeardTheEnemy);
} }
auto getHeardOriginWithError = [&] () -> Vector {
auto error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f;
auto origin = hearedEnemy->v.origin;
origin.x = origin.x + rg.get (-error, error);
origin.y = origin.y + rg.get (-error, error);
return origin;
};
// didn't bot already have an enemy ? take this one... // didn't bot already have an enemy ? take this one...
if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) {
m_lastEnemy = hearedEnemy; m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = hearedEnemy->v.origin; m_lastEnemyOrigin = getHeardOriginWithError ();
} }
// bot had an enemy, check if it's the heard one // bot had an enemy, check if it's the heard one
@ -3902,7 +3917,7 @@ void Bot::updateHearing () {
if (m_states & Sense::SeeingEnemy) { if (m_states & Sense::SeeingEnemy) {
return; return;
} }
m_lastEnemyOrigin = hearedEnemy->v.origin; m_lastEnemyOrigin = getHeardOriginWithError ();
} }
else { else {
// if bot had an enemy but the heard one is nearer, take it instead // if bot had an enemy but the heard one is nearer, take it instead
@ -3910,7 +3925,7 @@ void Bot::updateHearing () {
if (distanceSq > hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) { if (distanceSq > hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) {
m_lastEnemy = hearedEnemy; m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = hearedEnemy->v.origin; m_lastEnemyOrigin = getHeardOriginWithError ();
} }
else { else {
return; return;

View file

@ -12,6 +12,7 @@ ConVar cv_ignore_enemies ("ignore_enemies", "0", "Enables or disables searching
ConVar cv_check_enemy_rendering ("check_enemy_rendering", "0", "Enables or disables checking enemy rendering flags. Useful for some mods."); ConVar cv_check_enemy_rendering ("check_enemy_rendering", "0", "Enables or disables checking enemy rendering flags. Useful for some mods.");
ConVar cv_check_enemy_invincibility ("check_enemy_invincibility", "0", "Enables or disables checking enemy invincibility. Useful for some mods."); ConVar cv_check_enemy_invincibility ("check_enemy_invincibility", "0", "Enables or disables checking enemy invincibility. Useful for some mods.");
ConVar cv_stab_close_enemies ("stab_close_enemies", "1", "Enables or disables bot ability to stab the enemy with knife if bot is in good condition."); ConVar cv_stab_close_enemies ("stab_close_enemies", "1", "Enables or disables bot ability to stab the enemy with knife if bot is in good condition.");
ConVar cv_use_engine_pvs_check ("use_engine_pvs_check", "0", "Use engine to check potential visibility of a enemy.");
ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::GameRef); ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::GameRef);
ConVar sv_gravity ("sv_gravity", nullptr, Var::GameRef); ConVar sv_gravity ("sv_gravity", nullptr, Var::GameRef);
@ -293,7 +294,12 @@ bool Bot::lookupEnemies () {
// the old enemy is no longer visible or // the old enemy is no longer visible or
if (game.isNullEntity (newEnemy)) { if (game.isNullEntity (newEnemy)) {
auto set = game.getVisibilitySet (this, true); // setup potential visibility set from engine auto set = nullptr;
// setup potential visibility set from engine
if (cv_use_engine_pvs_check.bool_ ()) {
game.getVisibilitySet (this, true);
}
// ignore shielded enemies, while we have real one // ignore shielded enemies, while we have real one
edict_t *shieldEnemy = nullptr; edict_t *shieldEnemy = nullptr;
@ -306,7 +312,7 @@ bool Bot::lookupEnemies () {
} }
// check the engine PVS // check the engine PVS
if (!game.checkVisibility (interesting, set)) { if (cv_use_engine_pvs_check.bool_ () && !game.checkVisibility (interesting, set)) {
continue; continue;
} }
@ -336,7 +342,7 @@ bool Bot::lookupEnemies () {
player = client.ent; player = client.ent;
// check the engine PVS // check the engine PVS
if (!game.checkVisibility (player, set)) { if (cv_use_engine_pvs_check.bool_ () && !game.checkVisibility (player, set)) {
continue; continue;
} }
@ -514,7 +520,7 @@ bool Bot::lookupEnemies () {
} }
Vector Bot::getBodyOffsetError (float distance) { Vector Bot::getBodyOffsetError (float distance) {
if (game.isNullEntity (m_enemy) || distance < kDoubleSprayDistance) { if (game.isNullEntity (m_enemy) || distance < kSprayDistanceX2) {
return nullptr; return nullptr;
} }
@ -570,7 +576,12 @@ Vector Bot::getEnemyBodyOffset () {
else if (util.isPlayer (m_enemy)) { else if (util.isPlayer (m_enemy)) {
// now take in account different parts of enemy body // now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) { if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
const auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
// with to much recoil or using specific weapons choice to aim to the chest
if (distance > kSprayDistance && (isRecoilHigh () || usesSniperAWP () || usesShotgun ())) {
headshotPct = 0;
}
// now check is our skill match to aim at head, else aim at enemy body // now check is our skill match to aim at head, else aim at enemy body
if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) { if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) {
@ -639,10 +650,10 @@ Vector Bot::getCustomHeight (float distance) {
auto distanceIndex = DistanceIndex::Short; auto distanceIndex = DistanceIndex::Short;
// set distance index appropriate to distance // set distance index appropriate to distance
if (distance < 2048.0f && distance > kDoubleSprayDistance) { if (distance < 2048.0f && distance > kSprayDistanceX2) {
distanceIndex = DistanceIndex::Long; distanceIndex = DistanceIndex::Long;
} }
else if (distance > kSprayDistance && distance <= kDoubleSprayDistance) { else if (distance > kSprayDistance && distance <= kSprayDistanceX2) {
distanceIndex = DistanceIndex::Middle; distanceIndex = DistanceIndex::Middle;
} }
return { 0.0f, 0.0f, kOffsetRanges[m_weaponType][distanceIndex] }; return { 0.0f, 0.0f, kOffsetRanges[m_weaponType][distanceIndex] };
@ -834,24 +845,23 @@ bool Bot::needToPauseFiring (float distance) {
if (distance < kSprayDistance) { if (distance < kSprayDistance) {
return false; return false;
} }
else if (distance < kDoubleSprayDistance) { else if (distance < kSprayDistanceX2) {
offset = 2.75f; offset = 2.75f;
} }
else if ((m_states & Sense::SuspectEnemy) && distance < kDoubleSprayDistance) { else if ((m_states & Sense::SuspectEnemy) && distance < kSprayDistanceX2) {
return false; return false;
} }
const float xPunch = cr::sqrf (cr::deg2rad (pev->punchangle.x)); const float xPunch = cr::sqrf (cr::deg2rad (pev->punchangle.x));
const float yPunch = cr::sqrf (cr::deg2rad (pev->punchangle.y)); const float yPunch = cr::sqrf (cr::deg2rad (pev->punchangle.y));
const float interval = m_frameInterval;
const float tolerance = (100.0f - static_cast <float> (m_difficulty) * 25.0f) / 99.0f; const float tolerance = (100.0f - static_cast <float> (m_difficulty) * 25.0f) / 99.0f;
const float baseTime = distance > kDoubleSprayDistance ? 0.65f : 0.48f; const float baseTime = distance > kSprayDistance ? 0.65f : 0.48f;
const float maxRecoil = static_cast <float> (conf.getDifficultyTweaks (m_difficulty)->maxRecoil); const float maxRecoil = static_cast <float> (conf.getDifficultyTweaks (m_difficulty)->maxRecoil);
// check if we need to compensate recoil // check if we need to compensate recoil
if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) { if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) {
if (m_firePause < game.time ()) { if (m_firePause < game.time ()) {
m_firePause = game.time () + rg.get (baseTime, baseTime + maxRecoil * 0.01f * tolerance) - interval; m_firePause = game.time () + rg.get (baseTime, baseTime + maxRecoil * 0.01f * tolerance) - m_frameInterval;
} }
return true; return true;
} }
@ -1229,16 +1239,9 @@ void Bot::attackMovement () {
// only take cover when bomb is not planted and enemy can see the bot or the bot is VIP // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP
if (!game.is (GameFlags::CSDM)) { if (!game.is (GameFlags::CSDM)) {
if (m_retreatTime < game.time () && approach < 30 && !bots.isBombPlanted ()) { if ((m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
const auto enemyCone = util.getConeDeviation (m_enemy, pev->origin); m_moveSpeed = -pev->maxspeed;
const auto seeingEnemy = (m_states & Sense::SeeingEnemy); startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
const auto enemyWeaponIsSniper = (m_enemy->v.weapons & kSniperWeaponMask);
// make bot seek cover
if ((enemyCone > 0.8f && seeingEnemy) || (enemyWeaponIsSniper && enemyCone > 0.95f)) {
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, false);
m_moveSpeed = -pev->maxspeed;
}
} }
else if (approach < 50) { else if (approach < 50) {
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
@ -1292,7 +1295,7 @@ void Bot::attackMovement () {
if (isDucking () || isInNarrowPlace ()) { if (isDucking () || isInNarrowPlace ()) {
m_fightStyle = Fight::Stay; m_fightStyle = Fight::Stay;
} }
const auto pistolStrafeDistance = game.is (GameFlags::CSDM) ? kDoubleSprayDistance * 3.0f : kDoubleSprayDistance; const auto pistolStrafeDistance = game.is (GameFlags::CSDM) ? kSprayDistanceX2 * 3.0f : kSprayDistanceX2;
// fire hurts friend value here is from previous frame, but acceptable, and saves us alot of cpu cycles // fire hurts friend value here is from previous frame, but acceptable, and saves us alot of cpu cycles
if (approach < 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ()) if (approach < 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ())
@ -1317,7 +1320,7 @@ void Bot::attackMovement () {
}; };
auto strafeUpdateTime = [] () { auto strafeUpdateTime = [] () {
return game.time () + rg.get (0.8f, 1.25f); return game.time () + rg.get (0.3f, 1.0f);
}; };
// to start strafing, we have to first figure out if the target is on the left side or right side // to start strafing, we have to first figure out if the target is on the left side or right side
@ -1392,7 +1395,7 @@ void Bot::attackMovement () {
if (alreadyDucking) { if (alreadyDucking) {
m_duckTime = game.time () + m_frameInterval * 3.0f; m_duckTime = game.time () + m_frameInterval * 3.0f;
} }
else if ((distance > 768.0f && hasPrimaryWeapon ()) else if ((distance > kSprayDistanceX2 && hasPrimaryWeapon ())
&& (m_enemyParts & (Visibility::Head | Visibility::Body)) && (m_enemyParts & (Visibility::Head | Visibility::Body))
&& getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::SeekCover
&& getCurrentTaskId () != Task::Hunt) { && getCurrentTaskId () != Task::Hunt) {
@ -1409,7 +1412,7 @@ void Bot::attackMovement () {
} }
if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) { if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) {
if (distance < kDoubleSprayDistance) { if (distance < kSprayDistanceX2) {
if (rg.get (0, 1000) < rg.get (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { if (rg.get (0, 1000) < rg.get (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
pev->button |= IN_JUMP; pev->button |= IN_JUMP;
} }

View file

@ -496,6 +496,10 @@ int BotControl::cmdNodeOn () {
} }
if (graph.hasEditFlag (GraphEdit::On)) { if (graph.hasEditFlag (GraphEdit::On)) {
m_graphSaveVarValues.roundtime = mp_roundtime.float_ ();
m_graphSaveVarValues.freezetime = mp_freezetime.float_ ();
m_graphSaveVarValues.timelimit = mp_timelimit.float_ ();
mp_roundtime.set (9); mp_roundtime.set (9);
mp_freezetime.set (0); mp_freezetime.set (0);
mp_timelimit.set (0); mp_timelimit.set (0);
@ -511,6 +515,11 @@ int BotControl::cmdNodeOff () {
graph.clearEditFlag (GraphEdit::On | GraphEdit::Auto | GraphEdit::Noclip); graph.clearEditFlag (GraphEdit::On | GraphEdit::Auto | GraphEdit::Noclip);
enableDrawModels (false); enableDrawModels (false);
// revert cvars back to their values
mp_roundtime.set (m_graphSaveVarValues.roundtime);
mp_freezetime.set (m_graphSaveVarValues.freezetime);
mp_timelimit.set (m_graphSaveVarValues.timelimit);
msg ("Graph editor has been disabled."); msg ("Graph editor has been disabled.");
} }
else if (strValue (option) == "models") { else if (strValue (option) == "models") {

View file

@ -379,7 +379,7 @@ bool Game::checkVisibility (edict_t *ent, uint8_t *set) {
return false; return false;
} }
for (int i = 0; i < 48; ++i) { for (int i = 0; i < MAX_ENT_LEAFS; ++i) {
const auto leaf = ent->leafnums[i]; const auto leaf = ent->leafnums[i];
if (leaf == -1) { if (leaf == -1) {

View file

@ -14,7 +14,7 @@ ConVar cv_kick_after_player_connect ("kick_after_player_connect", "1", "Kick the
ConVar cv_quota ("quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <float> (kGameMaxPlayers)); ConVar cv_quota ("quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar cv_quota_mode ("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 cv_quota_mode ("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 cv_quota_match ("quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast <float> (kGameMaxPlayers)); ConVar cv_quota_match ("quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar cv_think_fps ("think_fps", "26.0", "Specifies how many times per second bot code will run.", true, 24.0f, 90.0f); ConVar cv_think_fps ("think_fps", "30.0", "Specifies how many times per second bot code will run.", true, 24.0f, 90.0f);
ConVar cv_think_fps_disable ("think_fps_disable", "0", "Allows to completely disable think fps on Xash3D.", true, 0.0f, 1.0f, Var::Xash3D); ConVar cv_think_fps_disable ("think_fps_disable", "0", "Allows to completely disable think fps on Xash3D.", true, 0.0f, 1.0f, Var::Xash3D);
ConVar cv_autokill_delay ("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 cv_autokill_delay ("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);

View file

@ -975,7 +975,7 @@ bool Bot::updateNavigation () {
const auto prevNodeIndex = m_previousNodes[0]; const auto prevNodeIndex = m_previousNodes[0];
// do a precise movement when very near // do a precise movement when very near
if (graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) { if (!isDucking () && graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
m_moveSpeed = pev->maxspeed * 0.4f; m_moveSpeed = pev->maxspeed * 0.4f;
// do not duck while not on ladder // do not duck while not on ladder
@ -1114,14 +1114,14 @@ bool Bot::updateNavigation () {
} }
float desiredDistanceSq = cr::sqrf (4.0f); float desiredDistanceSq = cr::sqrf (4.0f);
const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin); const float nodeDistanceSq = pev->origin.distanceSq2d (m_pathOrigin);
// initialize the radius for a special node type, where the node is considered to be reached // initialize the radius for a special node type, where the node is considered to be reached
if (m_pathFlags & NodeFlag::Lift) { if (m_pathFlags & NodeFlag::Lift) {
desiredDistanceSq = cr::sqrf (50.0f); desiredDistanceSq = cr::sqrf (50.0f);
} }
else if (isDucking () || (m_pathFlags & NodeFlag::Goal)) { else if (isDucking () || (m_pathFlags & NodeFlag::Goal)) {
desiredDistanceSq = cr::sqrf (25.0f); desiredDistanceSq = cr::sqrf (12.0f);
// on cs_ maps goals are usually hostages, so increase reachability distance for them, they (hostages) picked anyway // on cs_ maps goals are usually hostages, so increase reachability distance for them, they (hostages) picked anyway
if (game.mapIs (MapFlags::HostageRescue) && (m_pathFlags & NodeFlag::Goal)) { if (game.mapIs (MapFlags::HostageRescue) && (m_pathFlags & NodeFlag::Goal)) {

View file

@ -463,7 +463,6 @@ void Bot::updateLookAngles () {
m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f); m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f);
pev->v_angle = m_idealAngles; pev->v_angle = m_idealAngles;
pev->v_angle.clampAngles ();
updateBodyAngles (); updateBodyAngles ();
} }