diff --git a/cfg/addons/yapb/conf/yapb.cfg b/cfg/addons/yapb/conf/yapb.cfg index e290402..adbda2b 100644 --- a/cfg/addons/yapb/conf/yapb.cfg +++ b/cfg/addons/yapb/conf/yapb.cfg @@ -235,6 +235,13 @@ yb_pickup_best "1" // 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. // --- @@ -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. // --- -// 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. @@ -284,6 +291,13 @@ yb_check_enemy_invincibility "0" // 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. // --- @@ -352,6 +366,13 @@ yb_threadpool_workers "-1" // 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. // --- @@ -442,9 +463,9 @@ yb_quota_match "0" // // 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. diff --git a/inc/constant.h b/inc/constant.h index 5ef483e..065ea10 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -427,8 +427,8 @@ namespace TaskPri { constexpr auto kInfiniteDistance = 9999999.0f; constexpr auto kInvalidLightLevel = kInfiniteDistance; constexpr auto kGrenadeCheckTime = 0.6f; -constexpr auto kSprayDistance = 260.0f; -constexpr auto kDoubleSprayDistance = kSprayDistance * 2; +constexpr auto kSprayDistance = 360.0f; +constexpr auto kSprayDistanceX2 = kSprayDistance * 2; constexpr auto kMaxChatterRepeatInterval = 99.0f; constexpr auto kViewFrameUpdate = 1.0f / 30.0f; constexpr auto kGrenadeDamageRadius = 385.0f; diff --git a/inc/control.h b/inc/control.h index 4e0afba..6c514b4 100644 --- a/inc/control.h +++ b/inc/control.h @@ -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: StringArray m_args {}; Array m_cmds {}; diff --git a/inc/yapb.h b/inc/yapb.h index 3725253..eeb84cc 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -790,6 +790,11 @@ private: 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 bool usesRifle () const { return usesZoomableRifle () || m_weaponType == WeaponType::Rifle; @@ -834,6 +839,11 @@ private: bool usesKnife () const { return m_weaponType == WeaponType::Melee; } + + // checks if weapon recoil is high + bool isRecoilHigh () const { + return pev->punchangle.x < -1.45f; + } }; #include "config.h" diff --git a/src/botlib.cpp b/src/botlib.cpp index 3639962..70dd96b 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -1711,6 +1711,7 @@ void Bot::overrideConditions () { m_isReloading = false; } } + if (m_seeEnemyTime + 2.5f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) { m_moveSpeed = m_fearLevel > m_agressionLevel ? 0.0f : getShiftSpeed (); m_navTimeset = game.time (); @@ -1785,10 +1786,14 @@ void Bot::refreshEnemyPredict () { } const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f) - && m_shootTime + 2.5f > game.time (); + && m_shootTime + 1.5f > game.time (); - if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { - m_aimFlags |= AimFlags::LastEnemy; + if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { + 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_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 () { @@ -3869,7 +3874,7 @@ void Bot::updateHearing () { } // did the bot hear someone ? - if (hearedEnemy != nullptr && util.isPlayer (hearedEnemy)) { + if (util.isPlayer (hearedEnemy)) { // change to best weapon if heard something if (m_shootTime < game.time () - 5.0f && isOnFloor () @@ -3889,10 +3894,20 @@ void Bot::updateHearing () { 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... if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { m_lastEnemy = hearedEnemy; - m_lastEnemyOrigin = hearedEnemy->v.origin; + m_lastEnemyOrigin = getHeardOriginWithError (); } // bot had an enemy, check if it's the heard one @@ -3902,7 +3917,7 @@ void Bot::updateHearing () { if (m_states & Sense::SeeingEnemy) { return; } - m_lastEnemyOrigin = hearedEnemy->v.origin; + m_lastEnemyOrigin = getHeardOriginWithError (); } else { // 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 ()) { m_lastEnemy = hearedEnemy; - m_lastEnemyOrigin = hearedEnemy->v.origin; + m_lastEnemyOrigin = getHeardOriginWithError (); } else { return; diff --git a/src/combat.cpp b/src/combat.cpp index 2d54472..4d923d8 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -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_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_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 sv_gravity ("sv_gravity", nullptr, Var::GameRef); @@ -293,7 +294,12 @@ bool Bot::lookupEnemies () { // the old enemy is no longer visible or 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 edict_t *shieldEnemy = nullptr; @@ -306,7 +312,7 @@ bool Bot::lookupEnemies () { } // check the engine PVS - if (!game.checkVisibility (interesting, set)) { + if (cv_use_engine_pvs_check.bool_ () && !game.checkVisibility (interesting, set)) { continue; } @@ -336,7 +342,7 @@ bool Bot::lookupEnemies () { player = client.ent; // check the engine PVS - if (!game.checkVisibility (player, set)) { + if (cv_use_engine_pvs_check.bool_ () && !game.checkVisibility (player, set)) { continue; } @@ -514,7 +520,7 @@ bool Bot::lookupEnemies () { } Vector Bot::getBodyOffsetError (float distance) { - if (game.isNullEntity (m_enemy) || distance < kDoubleSprayDistance) { + if (game.isNullEntity (m_enemy) || distance < kSprayDistanceX2) { return nullptr; } @@ -570,7 +576,12 @@ Vector Bot::getEnemyBodyOffset () { else if (util.isPlayer (m_enemy)) { // now take in account different parts of enemy 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 if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) { @@ -639,10 +650,10 @@ Vector Bot::getCustomHeight (float distance) { auto distanceIndex = DistanceIndex::Short; // set distance index appropriate to distance - if (distance < 2048.0f && distance > kDoubleSprayDistance) { + if (distance < 2048.0f && distance > kSprayDistanceX2) { distanceIndex = DistanceIndex::Long; } - else if (distance > kSprayDistance && distance <= kDoubleSprayDistance) { + else if (distance > kSprayDistance && distance <= kSprayDistanceX2) { distanceIndex = DistanceIndex::Middle; } return { 0.0f, 0.0f, kOffsetRanges[m_weaponType][distanceIndex] }; @@ -834,24 +845,23 @@ bool Bot::needToPauseFiring (float distance) { if (distance < kSprayDistance) { return false; } - else if (distance < kDoubleSprayDistance) { + else if (distance < kSprayDistanceX2) { offset = 2.75f; } - else if ((m_states & Sense::SuspectEnemy) && distance < kDoubleSprayDistance) { + else if ((m_states & Sense::SuspectEnemy) && distance < kSprayDistanceX2) { return false; } const float xPunch = cr::sqrf (cr::deg2rad (pev->punchangle.x)); const float yPunch = cr::sqrf (cr::deg2rad (pev->punchangle.y)); - const float interval = m_frameInterval; const float tolerance = (100.0f - static_cast (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 (conf.getDifficultyTweaks (m_difficulty)->maxRecoil); // check if we need to compensate recoil if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) { 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; } @@ -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 if (!game.is (GameFlags::CSDM)) { - if (m_retreatTime < game.time () && approach < 30 && !bots.isBombPlanted ()) { - const auto enemyCone = util.getConeDeviation (m_enemy, pev->origin); - const auto seeingEnemy = (m_states & Sense::SeeingEnemy); - 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; - } + if ((m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) { + m_moveSpeed = -pev->maxspeed; + startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true); } else if (approach < 50) { m_moveSpeed = 0.0f; @@ -1292,7 +1295,7 @@ void Bot::attackMovement () { if (isDucking () || isInNarrowPlace ()) { 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 if (approach < 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ()) @@ -1317,7 +1320,7 @@ void Bot::attackMovement () { }; 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 @@ -1392,7 +1395,7 @@ void Bot::attackMovement () { if (alreadyDucking) { 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)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { @@ -1409,7 +1412,7 @@ void Bot::attackMovement () { } 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)) { pev->button |= IN_JUMP; } diff --git a/src/control.cpp b/src/control.cpp index cbaf6df..b276cee 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -496,6 +496,10 @@ int BotControl::cmdNodeOn () { } 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_freezetime.set (0); mp_timelimit.set (0); @@ -511,6 +515,11 @@ int BotControl::cmdNodeOff () { graph.clearEditFlag (GraphEdit::On | GraphEdit::Auto | GraphEdit::Noclip); 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."); } else if (strValue (option) == "models") { diff --git a/src/engine.cpp b/src/engine.cpp index 42ea4be..f61ecf4 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -379,7 +379,7 @@ bool Game::checkVisibility (edict_t *ent, uint8_t *set) { return false; } - for (int i = 0; i < 48; ++i) { + for (int i = 0; i < MAX_ENT_LEAFS; ++i) { const auto leaf = ent->leafnums[i]; if (leaf == -1) { diff --git a/src/manager.cpp b/src/manager.cpp index 8e3d3e7..e6aad41 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -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 (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_match ("quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast (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_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); diff --git a/src/navigate.cpp b/src/navigate.cpp index c37f8d5..e31da6c 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -975,7 +975,7 @@ bool Bot::updateNavigation () { const auto prevNodeIndex = m_previousNodes[0]; // 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; // do not duck while not on ladder @@ -1114,14 +1114,14 @@ bool Bot::updateNavigation () { } 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 if (m_pathFlags & NodeFlag::Lift) { desiredDistanceSq = cr::sqrf (50.0f); } 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 if (game.mapIs (MapFlags::HostageRescue) && (m_pathFlags & NodeFlag::Goal)) { diff --git a/src/vision.cpp b/src/vision.cpp index 9f8d162..bab41d2 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -463,7 +463,6 @@ void Bot::updateLookAngles () { m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f); pev->v_angle = m_idealAngles; - pev->v_angle.clampAngles (); updateBodyAngles (); }