From e717710bd1db80453c63ddbf35f212cb054686b8 Mon Sep 17 00:00:00 2001 From: commandcobra7 <91374215+commandcobra7@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:53:16 +0300 Subject: [PATCH] nav: fall controls are not good. (#656) nav: fall controls are not good. nav: increased behavior rate in finding the best goal. nav: avoiding the door after hitting it. combat: if the enemy is using a sniper rifle, move. combat: enemy group functionality has been improved. combat: if we are doing a camping task, do not switch to a knife at close range. aim: slight changes to enemy prediction. --------- Co-authored-by: jeefo --- inc/manager.h | 2 +- inc/yapb.h | 9 +-- src/botlib.cpp | 78 +++++++++--------- src/combat.cpp | 65 +++++++-------- src/manager.cpp | 8 +- src/navigate.cpp | 206 ++++++++++++++++++++++++++--------------------- src/support.cpp | 15 +++- src/tasks.cpp | 44 +++++----- src/vision.cpp | 29 ++++--- 9 files changed, 245 insertions(+), 211 deletions(-) diff --git a/inc/manager.h b/inc/manager.h index c1f026d..829bbf2 100644 --- a/inc/manager.h +++ b/inc/manager.h @@ -128,7 +128,7 @@ public: bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); bool balancedKickRandom (bool decQuota); bool hasCustomCSDMSpawnEntities (); - bool isLineBlockedBySmoke (const Vector &from, const Vector &to, float grenadeBloat = 1.0f); + bool isLineBlockedBySmoke (const Vector &from, const Vector &to); bool isFrameSkipDisabled (); public: diff --git a/inc/yapb.h b/inc/yapb.h index 7ac7cbe..867ab48 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -237,6 +237,7 @@ private: float m_prevSpeed {}; // speed some frames before float m_prevVelocity {}; // velocity some frames before float m_timeDoorOpen {}; // time to next door open check + float m_timeHitDoor {}; // specific time after hitting the door float m_lastChatTime {}; // time bot last chatted float m_timeLogoSpray {}; // time bot last spray logo float m_knifeAttackTime {}; // time to rush with knife (at the beginning of the round) @@ -284,7 +285,6 @@ private: float m_playServerTime {}; // time bot spent in the game float m_changeViewTime {}; // timestamp to change look at while at freezetime float m_breakableTime {}; // breakable acquired time - float m_stuckTimestamp {}; // last time was stuck float m_timeDebugUpdateTime {}; // time to update last debug timestamp float m_lastVictimTime {}; // time when bot killed an enemy float m_killsInterval {}; // interval between kills @@ -437,7 +437,7 @@ private: bool isEnemyNoTarget (edict_t *enemy); bool isEnemyInDarkArea (edict_t *enemy); bool isFriendInLineOfFire (float distance); - bool isGroupOfEnemies (const Vector &location, int numEnemies = 1, float radius = 256.0f); + bool isGroupOfEnemies (const Vector &location); bool isPenetrableObstacle (const Vector &dest); bool isPenetrableObstacle1 (const Vector &dest, int penetratePower); bool isPenetrableObstacle2 (const Vector &dest, int penetratePower); @@ -831,11 +831,6 @@ 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; diff --git a/src/botlib.cpp b/src/botlib.cpp index 21fbf1b..c451516 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -85,7 +85,7 @@ void Bot::avoidGrenades () { const auto &activeGrenades = bots.getActiveGrenades (); // find all grenades on the map - for (auto pent : activeGrenades) { + for (const auto &pent : activeGrenades) { if (pent->v.effects & EF_NODRAW) { continue; } @@ -96,10 +96,7 @@ void Bot::avoidGrenades () { } auto model = pent->v.model.str (9); - if (m_preventFlashing < game.time () - && cv_whose_your_daddy - && model == kFlashbangModelName) { - + if (m_preventFlashing < game.time () && model == kFlashbangModelName) { // don't look at flash bang if (!(m_states & Sense::SeeingEnemy)) { m_lookAt.y = cr::wrapAngle ((game.getEntityOrigin (pent) - getEyesPos ()).angles ().y + 180.0f); @@ -180,9 +177,7 @@ void Bot::checkBreakablesAround () { || !game.hasBreakables () || m_seeEnemyTime + 4.0f > game.time () || !game.isNullEntity (m_enemy) - || !hasPrimaryWeapon () - || (m_aimFlags & (AimFlags::Danger | AimFlags::PredictPath | AimFlags::Enemy)) - || getCurrentTaskId () != Task::Normal) { + || !hasPrimaryWeapon ()) { return; } const auto radius = cv_object_destroy_radius.as (); @@ -349,13 +344,13 @@ void Bot::updatePickups () { bool itemExists = false; auto pickupItem = m_pickupItem; - for (auto &ent : interesting) { + for (const auto &ent : interesting) { // in the periods of updating interesting entities we can get fake ones, that already were picked up, so double check if drawn if (ent->v.effects & EF_NODRAW) { continue; } - const Vector &origin = game.getEntityOrigin (ent); + const auto &origin = game.getEntityOrigin (ent); // too far from us ? if (pev->origin.distanceSq (origin) > radiusSq) { @@ -595,7 +590,7 @@ void Bot::updatePickups () { m_defendedBomb = true; const int index = findDefendNode (origin); - const Path &path = graph[index]; + const auto &path = graph[index]; const float bombTimer = mp_c4timer.as (); const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); @@ -638,7 +633,7 @@ void Bot::updatePickups () { // don't steal hostage from human teammate (hack) if (allowPickup) { - for (auto &client : util.getClients ()) { + for (const auto &client : util.getClients ()) { if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) && client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) { allowPickup = false; @@ -805,9 +800,9 @@ Vector Bot::getCampDirection (const Vector &dest) { float nearestDistance = kInfiniteDistance; int lookAtNode = kInvalidNodeIndex; - const Path &path = graph[tempIndex]; + const auto &path = graph[tempIndex]; - for (auto &link : path.links) { + for (const auto &link : path.links) { if (link.index == kInvalidNodeIndex) { continue; } @@ -1658,7 +1653,8 @@ void Bot::overrideConditions () { const auto tid = getCurrentTaskId (); // check if we need to escape from bomb - if (game.mapIs (MapFlags::Demolition) + if ((tid == Task::Normal || tid == Task::MoveToPosition) + && game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_isAlive && tid != Task::EscapeFromBomb @@ -1717,8 +1713,8 @@ void Bot::overrideConditions () { } // special handling for reloading - if (!bots.isRoundOver () && - tid == Task::Normal + if (!bots.isRoundOver () + && tid == Task::Normal && m_reloadState != Reload::None && m_isReloading && !isDucking () @@ -1775,7 +1771,7 @@ void Bot::syncUpdatePredictedIndex () { const float distToBotSq = botOrigin.distanceSq (graph[index].origin); - if (vistab.visible (currentNodeIndex, index) && distToBotSq > 128.0f && distToBotSq < cr::sqrf (768.0f)) { + if (vistab.visible (currentNodeIndex, index) && distToBotSq < cr::sqrf (2048.0f)) { bestIndex = index; return false; } @@ -1812,7 +1808,8 @@ void Bot::refreshEnemyPredict () { && distanceToLastEnemySq < cr::sqrf (256.0f) && m_shootTime + 1.5f > game.time (); - if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { + if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger)) + && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { m_aimFlags |= AimFlags::LastEnemy; } } @@ -1993,8 +1990,8 @@ void Bot::filterTasks () { // increase/decrease fear/aggression if bot uses a sniping weapon to be more careful if (usesSniper ()) { - tempFear = tempFear * 1.2f; - tempAgression = tempAgression * 0.6f; + tempFear = tempFear * 1.5f; + tempAgression = tempAgression * 0.5f; } auto &filter = bots.getFilters (); @@ -2675,7 +2672,7 @@ void Bot::checkRadioQueue () { switch (getCurrentTaskId ()) { case Task::Normal: if (getTask ()->data != kInvalidNodeIndex && rg.chance (70)) { - const Path &path = graph[getTask ()->data]; + const auto &path = graph[getTask ()->data]; if (path.flags & NodeFlag::Goal) { if (m_hasC4) { @@ -2787,7 +2784,7 @@ void Bot::checkRadioQueue () { int bombPoint = kInvalidNodeIndex; // find nearest bomb node to player - for (auto &point : graph.m_goalPoints) { + for (const auto &point : graph.m_goalPoints) { distanceSq = graph[point].origin.distanceSq (m_radioEntity->v.origin); if (distanceSq < nearestDistanceSq) { @@ -2932,7 +2929,7 @@ void Bot::frame () { } if (bots.isBombPlanted () && m_team == Team::CT && m_isAlive) { - const Vector &bombPosition = graph.getBombOrigin (); + const auto &bombPosition = graph.getBombOrigin (); if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb @@ -3057,9 +3054,9 @@ void Bot::logicDuringFreezetime () { return; } - if (rg.chance (15) && m_jumpTime + rg (1.0f, 2.0f) < game.time ()) { + if (rg.chance (15) && m_jumpTime < game.time ()) { pev->button |= IN_JUMP; - m_jumpTime = game.time (); + m_jumpTime = game.time () + rg (1.0f, 2.0f); } static Array players {}; players.clear (); @@ -3189,7 +3186,7 @@ void Bot::checkSpawnConditions () { void Bot::logic () { // this function gets called each frame and is the core of all bot ai. from here all other subroutines are called - float movedDistance = 2.0f; // length of different vector (distance bot moved) + float movedDistance = 4.0f; // length of different vector (distance bot moved) resetMovement (); @@ -3251,7 +3248,7 @@ void Bot::logic () { else if (!hasFriendNearby && rg.chance (50) && game.getTeam (m_enemy) != m_team - && isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) { + && isGroupOfEnemies (m_enemy->v.origin)) { pushChatterMessage (Chatter::ScaredEmotion); } @@ -3303,8 +3300,8 @@ void Bot::logic () { setIdealReactionTimers (); // calculate 2 direction vectors, 1 without the up/down component - const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval); - const Vector &dirNormal = dirOld.normalize2d_apx (); + const auto &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval); + const auto &dirNormal = dirOld.normalize2d_apx (); m_moveAngles = dirOld.angles (); m_moveAngles.clampAngles (); @@ -3340,8 +3337,17 @@ void Bot::logic () { // don't duck to get away faster pev->button &= ~IN_DUCK; - m_moveSpeed = -pev->maxspeed; - m_strafeSpeed = pev->maxspeed * static_cast (m_needAvoidGrenade); + Vector right {}, forward {}; + pev->v_angle.angleVectors (&forward, &right, nullptr); + + const auto &front = forward * -pev->maxspeed * 0.2f; + const auto &side = right * pev->maxspeed * static_cast (m_needAvoidGrenade) * 0.2f; + const auto &spot = pev->origin + front + side + pev->velocity * m_frameInterval; + + if (!isDeadlyMove (spot)) { + m_moveSpeed = -pev->maxspeed; + m_strafeSpeed = pev->maxspeed * static_cast (m_needAvoidGrenade); + } } // ensure we're not stuck picking something @@ -3836,7 +3842,7 @@ Vector Bot::isBombAudible () { if (m_difficulty > Difficulty::Hard) { return graph.getBombOrigin (); } - const Vector &bombOrigin = graph.getBombOrigin (); + const auto &bombOrigin = graph.getBombOrigin (); const float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.as ()) * 100.0f; float desiredRadius = 768.0f; @@ -3865,8 +3871,8 @@ Vector Bot::isBombAudible () { bool Bot::canRunHeavyWeight () { constexpr auto kInterval = 1.0f / 10.0f; - if (m_heavyTimestamp + kInterval < game.time ()) { - m_heavyTimestamp = game.time (); + if (m_heavyTimestamp < game.time ()) { + m_heavyTimestamp = game.time () + kInterval; return true; } @@ -3981,7 +3987,7 @@ void Bot::updateHearing () { float nearestDistanceSq = kInfiniteDistance; // do not hear to other enemies if just tracked old one - if (m_timeNextTracking < game.time () && util.isAlive (m_lastEnemy) && m_lastEnemy == m_trackingEdict) { + if (m_timeNextTracking < game.time () && m_lastEnemy == m_trackingEdict && util.isAlive (m_lastEnemy)) { m_hearedEnemy = m_lastEnemy; m_lastEnemyOrigin = m_lastEnemy->v.origin; diff --git a/src/combat.cpp b/src/combat.cpp index 948a669..7c79731 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -133,10 +133,11 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) { if (!cv_check_darkness && game.isNullEntity (enemy)) { return false; } + const auto &v = enemy->v; const auto scolor = illum.getSkyColor (); // check if node near the enemy have a degraded light level - const auto enemyNodeIndex = graph.getNearest (enemy->v.origin); + const auto enemyNodeIndex = graph.getNearest (v.origin); if (!graph.exists (enemyNodeIndex)) { return false; @@ -149,9 +150,9 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) { } bool enemySemiTransparent = false; - const bool enemyHasGun = (enemy->v.weapons & kPrimaryWeaponMask) || (enemy->v.weapons & kSecondaryWeaponMask); - const bool enemyHasFlashlightEnabled = !!(enemy->v.effects & EF_DIMLIGHT); - const bool enemyIsAttacking = !!(enemy->v.oldbuttons & IN_ATTACK); + const bool enemyHasGun = (v.weapons & kPrimaryWeaponMask) || (v.weapons & kSecondaryWeaponMask); + const bool enemyIsAttacking = (v.button & IN_ATTACK) || (v.oldbuttons & IN_ATTACK); + const bool enemyHasFlashlightEnabled = !!(v.effects & EF_DIMLIGHT); if (!m_usesNVG && ((llevel < 3.0f && scolor > 50.0f) || (llevel < 25.0f && scolor <= 50.0f)) && !enemyHasFlashlightEnabled && (!enemyIsAttacking || !enemyHasGun)) { @@ -163,7 +164,7 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) { enemySemiTransparent = true; } TraceResult result {}; - game.testLine (getEyesPos (), enemy->v.origin, m_isCreature ? TraceIgnore::None : TraceIgnore::Everything, ent (), &result); + game.testLine (getEyesPos (), v.origin, m_isCreature ? TraceIgnore::None : TraceIgnore::Everything, ent (), &result); return (result.flFraction <= 1.0f && result.pHit == enemy && (m_usesNVG || !enemySemiTransparent)); } @@ -193,7 +194,7 @@ bool Bot::checkBodyPartsWithOffsets (edict_t *target) { auto self = pev->pContainingEntity; // creatures can't hurt behind anything - const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ?TraceIgnore::Monsters : TraceIgnore::Everything); + const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ? TraceIgnore::Monsters : TraceIgnore::Everything); const auto hitsTarget = [&] () -> bool { return result.flFraction >= 1.0f || result.pHit == target; @@ -390,7 +391,7 @@ bool Bot::lookupEnemies () { m_states |= Sense::SuspectEnemy; const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f - && pev->origin.distanceSq2d (m_lastEnemyOrigin) < cr::sqrf (256.0f) + && m_lastEnemyOrigin.distanceSq (pev->origin) < cr::sqrf (256.0f) && m_shootTime + 1.5f > game.time (); if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger)) @@ -1244,10 +1245,9 @@ void Bot::fireWeapons () { if (cv_stab_close_enemies && m_difficulty >= Difficulty::Normal && m_healthValue > 80.0f && !game.isNullEntity (m_enemy) - && m_healthValue >= m_enemy->v.health && distance < 100.0f - && !isOnLadder () - && !isGroupOfEnemies (pev->origin)) { + && !isGroupOfEnemies (pev->origin) + && getCurrentTaskId () != Task::Camp) { selectWeapons (distance, selectIndex, selectId, choosenWeapon); return; @@ -1428,13 +1428,14 @@ void Bot::attackMovement () { approach = 49; } } + const bool isEnemyCone = isInViewCone (m_enemy->v.origin); // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP if (!game.is (GameFlags::CSDM) && !isKnifeMode ()) { if ((m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () - && (isInViewCone (m_enemy->v.origin) || m_isVIP || m_isReloading)) { + && (isEnemyCone || m_isVIP || m_isReloading)) { if (m_retreatTime < game.time ()) { startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true); @@ -1467,10 +1468,7 @@ void Bot::attackMovement () { m_fightStyle = Fight::Strafe; } else if (distanceSq < cr::sqrf (1024.0f)) { - if (isGroupOfEnemies (m_enemy->v.origin)) { - m_fightStyle = Fight::Strafe; - } - else if (rand < (usesSubmachine () ? 50 : 30)) { + if (rand < (usesSubmachine () ? 50 : 30)) { m_fightStyle = Fight::Strafe; } else { @@ -1478,10 +1476,7 @@ void Bot::attackMovement () { } } else { - if (isGroupOfEnemies (m_enemy->v.origin)) { - m_fightStyle = Fight::Strafe; - } - else if (rand < (usesSubmachine () ? 80 : 90)) { + if (rand < (usesSubmachine () ? 80 : 90)) { m_fightStyle = Fight::Stay; } else { @@ -1505,7 +1500,12 @@ void Bot::attackMovement () { // 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 ()) && distanceSq < cr::sqrf (pistolStrafeDistance) - && isInViewCone (m_enemyOrigin))) { + && isEnemyCone)) { + m_fightStyle = Fight::Strafe; + } + const auto enemyWeaponIsSniper = (m_enemy->v.weapons & kSniperWeaponMask); + + if (enemyWeaponIsSniper && isEnemyCone) { m_fightStyle = Fight::Strafe; } m_lastFightStyleCheck = game.time () + 3.0f; @@ -1515,7 +1515,7 @@ void Bot::attackMovement () { m_moveSpeed = -pev->maxspeed; } - if (usesKnife () && isInViewCone (m_enemy->v.origin)) { + if (usesKnife () && isEnemyCone) { m_fightStyle = Fight::Strafe; } @@ -1624,7 +1624,7 @@ void Bot::attackMovement () { if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) { if (distanceSq < cr::sqrf (kSprayDistanceX2)) { - if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { + if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isEnemyCone) { pev->button |= IN_JUMP; } } @@ -2003,28 +2003,29 @@ void Bot::updateTeamCommands () { m_timeTeamOrder = game.time () + rg (15.0f, 30.0f); } -bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) { +bool Bot::isGroupOfEnemies (const Vector &location) { int numPlayers = 0; // needs a square radius - const float radiusSq = cr::sqrf (radius); + const float radiusSq = cr::sqrf (768.0f); // search the world for enemy players... for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team || client.ent == ent ()) { continue; } if (client.ent->v.origin.distanceSq (location) < radiusSq) { - if (client.team == m_team) { - return false; // don't target our teammates... - } - - if (numPlayers++ > numEnemies) { - return true; + if (!seesEntity (client.origin)) { + continue; } + ++numPlayers; } } + + if (numPlayers < 2) { + return false; + } return false; } @@ -2541,7 +2542,7 @@ bool Bot::isEnemyNoticeable (float range) { if (isCrouching) { // crouching and motionless - very tough to notice closeChance = 80.0f; - farChance = 5.0f; // takes about three seconds to notice (50% chance) + farChance = 5.0f; // takes about three seconds to notice (50% chance) } // standing else { diff --git a/src/manager.cpp b/src/manager.cpp index b0bf6bc..9d0bd16 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1509,6 +1509,7 @@ void Bot::newRound () { m_lastBreakable = nullptr; m_timeDoorOpen = 0.0f; + m_timeHitDoor = 0.0f; for (auto &fall : m_checkFallPoint) { fall.clear (); @@ -1624,7 +1625,6 @@ void Bot::newRound () { m_dodgeStrafeDir = Dodge::None; m_fightStyle = Fight::None; m_lastFightStyleCheck = 0.0f; - m_stuckTimestamp = 0.0f; m_checkWeaponSwitch = true; m_checkKnifeSwitch = true; @@ -2209,11 +2209,11 @@ void BotThreadWorker::startup (int workers) { m_botWorker.startup (static_cast (requestedThreads)); } -bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, float grenadeBloat) { +bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to) { if (m_activeGrenades.empty ()) { return false; } - constexpr auto kSmokeGrenadeRadius = 115; + constexpr auto kSmokeGrenadeRadius = 115.0f; // distance along line of sight covered by smoke float totalSmokedLength = 0.0f; @@ -2241,7 +2241,7 @@ bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, flo continue; } - const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius) * cr::sqrf (grenadeBloat); + const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius); const Vector &smokeOrigin = game.getEntityOrigin (pent); Vector toGrenade = smokeOrigin - from; diff --git a/src/navigate.cpp b/src/navigate.cpp index 14f3634..0d86cca 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -99,6 +99,7 @@ int Bot::findBestGoal () { } return findGoalPost (hasMoreHostagesAround ? GoalTactic::Goal : GoalTactic::RescueHostage, defensiveNodes, offensiveNodes); } + constexpr float kBehaviorBase = 30.0f; const auto difficulty = static_cast (m_difficulty); offensive = m_agressionLevel * 100.0f; @@ -107,27 +108,27 @@ int Bot::findBestGoal () { if (game.mapIs (MapFlags::Assassination | MapFlags::HostageRescue)) { if (m_team == Team::Terrorist) { if (m_personality == Personality::Rusher) { - defensive -= 25.0f - difficulty * 0.5f; - offensive += 25.0f + difficulty * 5.0f; + defensive -= kBehaviorBase - difficulty * 0.5f; + offensive += kBehaviorBase + difficulty * 5.0f; } else if (m_personality == Personality::Normal && rg.chance (40)) { - defensive -= 25.0f; - offensive += 25.0f; + defensive -= kBehaviorBase; + offensive += kBehaviorBase; } else { - defensive += 25.0f; - offensive -= 25.0f; + defensive += kBehaviorBase; + offensive -= kBehaviorBase; } } else if (m_team == Team::CT) { // on hostage maps force more bots to save hostages if (game.mapIs (MapFlags::HostageRescue)) { - defensive -= 25.0f - difficulty * 0.5f; - offensive += 25.0f + difficulty * 5.0f; + defensive -= kBehaviorBase - difficulty * 0.5f; + offensive += kBehaviorBase + difficulty * 5.0f; } else { - defensive -= 25.0f; - offensive += 25.0f; + defensive -= kBehaviorBase; + offensive += kBehaviorBase; } } } @@ -140,8 +141,8 @@ int Bot::findBestGoal () { } return m_chosenGoalIndex = findBombNode (); } - defensive += 25.0f + difficulty * 4.0f; - offensive -= 25.0f - difficulty * 0.5f; + defensive += kBehaviorBase + difficulty * 5.0f; + offensive -= kBehaviorBase - difficulty * 0.5f; if (m_personality != Personality::Rusher) { defensive += 10.0f; @@ -155,12 +156,12 @@ int Bot::findBestGoal () { } else if (game.mapIs (MapFlags::Escape)) { if (m_team == Team::Terrorist) { - offensive += 25.0f + difficulty * 4.0f; - defensive -= 25.0f - difficulty * 0.5f; + offensive += kBehaviorBase + difficulty * 5.0f; + defensive -= kBehaviorBase - difficulty * 0.5f; } else if (m_team == Team::CT) { - offensive -= 25.0f - difficulty * 4.5f; - defensive += 25.0f + difficulty * 0.5f; + offensive -= kBehaviorBase - difficulty * 5.0f; + defensive += kBehaviorBase + difficulty * 0.5f; } } @@ -188,6 +189,7 @@ int Bot::findBestGoal () { } if (goalDesire > tacticChoice) { + tacticChoice = goalDesire; tactic = GoalTactic::Goal; } return findGoalPost (tactic, defensiveNodes, offensiveNodes); @@ -230,7 +232,7 @@ int Bot::findBestGoalWhenBombAction () { m_defendedBomb = true; result = findDefendNode (bombOrigin); - const Path &path = graph[result]; + const auto &path = graph[result]; const float bombTimer = mp_c4timer.as (); const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); @@ -258,7 +260,11 @@ int Bot::findBestGoalWhenBombAction () { } int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { - int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; + int goalChoices[4] {}; + + for (int i = 0; i < 4; ++i) { + goalChoices[i] = kInvalidNodeIndex; + } if (tactic == GoalTactic::Defensive && !(*defensive).empty ()) { // careful goal postProcessGoals (*defensive, goalChoices); @@ -282,7 +288,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { float nearestDistanceSq = kInfiniteDistance; int count = 0; - for (auto &point : graph.m_goalPoints) { + for (const auto &point : graph.m_goalPoints) { const float distanceSq = graph[point].origin.distanceSq (pev->origin); if (distanceSq > cr::sqrf (1024.0f)) { @@ -313,7 +319,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { float nearestDistanceSq = kInfiniteDistance; int count = 0; - for (auto &point : graph.m_rescuePoints) { + for (const auto &point : graph.m_rescuePoints) { const float distanceSq = graph[point].origin.distanceSq (pev->origin); if (distanceSq < nearestDistanceSq) { @@ -334,10 +340,6 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { } ensureCurrentNodeIndex (); - if (goalChoices[0] == kInvalidNodeIndex) { - return m_chosenGoalIndex = graph.random (); - } - // rusher bots does not care any danger (idea from pbmm) if (m_personality == Personality::Rusher) { const auto randomGoal = goalChoices[rg (0, 3)]; @@ -468,39 +470,23 @@ void Bot::ignoreCollision () { } void Bot::doPlayerAvoidance (const Vector &normal) { - if (cv_has_team_semiclip || game.is (GameFlags::FreeForAll)) { + if (isOnLadder () || cv_has_team_semiclip || game.is (GameFlags::FreeForAll)) { return; // no player avoiding when with semiclip plugin } - m_hindrance = nullptr; float distanceSq = cr::sqrf (512.0f); - if (isOnLadder ()) { - return; - } const auto ownPrio = bots.getPlayerPriority (ent ()); // find nearest player to bot for (const auto &client : util.getClients ()) { - - // need only good meat - if (!(client.flags & ClientFlags::Used)) { - continue; - } - - // and still alive meet - if (!(client.flags & ClientFlags::Alive)) { - continue; - } - - // our team, alive and not myself? - if (client.team != m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) { continue; } const auto otherPrio = bots.getPlayerPriority (client.ent); // give some priorities to bot avoidance - if (ownPrio > otherPrio) { + if (ownPrio < otherPrio) { continue; } @@ -509,7 +495,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { const auto avoidPrio = bots.getPlayerPriority (m_hindrance); // ignore because we're already avoiding someone better - if (avoidPrio > otherPrio) { + if (avoidPrio < otherPrio) { continue; } } @@ -525,7 +511,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { if (game.isNullEntity (m_hindrance)) { return; } - const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f); + const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f); // use our movement angles, try to predict where we should be next frame Vector right {}, forward {}; @@ -551,10 +537,13 @@ void Bot::doPlayerAvoidance (const Vector &normal) { setStrafeSpeed (normal, -pev->maxspeed); } - if (distanceSq < cr::sqrf (80.0f)) { + if (distanceSq < cr::sqrf (96.0f)) { if ((dir | forward.normalize2d_apx ()) < 0.0f) { m_moveSpeed = -pev->maxspeed; } + else { + m_moveSpeed = pev->maxspeed; + } } } } @@ -568,7 +557,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // standing still, no need to check? if (m_lastCollTime < game.time () && getCurrentTaskId () != Task::Attack) { // didn't we move enough previously? - if (movedDistance < 2.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) { + if (movedDistance < 4.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) { m_prevTime = game.time (); // then consider being stuck m_isStuck = true; @@ -862,7 +851,7 @@ void Bot::checkFall () { } } else if (!isOnLadder () && !isInWater ()) { - if (!m_checkFallPoint[0].empty () && !m_checkFallPoint[1].empty ()) { + if (!m_checkFallPoint[0].empty () || !m_checkFallPoint[1].empty ()) { m_checkFall = true; } } @@ -882,12 +871,12 @@ void Bot::checkFall () { && baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) { fixFall = true; } - else if (pev->origin.z + 128.0f < m_checkFallPoint[1].z && pev->origin.z + 128.0f < m_checkFallPoint[0].z) { + else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f || m_checkFallPoint[0].z > pev->origin.z + 128.0f) { fixFall = true; } else if (m_currentNodeIndex != kInvalidNodeIndex - && nowDistanceSq <= cr::sqrf (32.0f) - && pev->origin.z + 64.0f < m_checkFallPoint[1].z) { + && nowDistanceSq > cr::sqrf (16.0f) + && m_checkFallPoint[1].z > pev->origin.z + 62.0f) { fixFall = true; } @@ -925,6 +914,26 @@ void Bot::moveToGoal () { pev->button |= IN_DUCK; } } + + // press jump button if we need to leave the ladder + if (!(m_pathFlags & NodeFlag::Ladder) + && isPreviousLadder () + && isOnFloor () + && isOnLadder () + && m_moveSpeed > 50.0f + && pev->velocity.lengthSq () < 50.0f) { + + pev->button |= IN_JUMP; + m_jumpTime = game.time () + 1.0f; + } + const auto distanceSq2d = m_destOrigin.distanceSq2d (pev->origin + pev->velocity * m_frameInterval); + + if (distanceSq2d < cr::sqrf (m_moveSpeed) * m_frameInterval && getTask ()->data != kInvalidNodeIndex) { + m_moveSpeed = distanceSq2d; + } + if (m_moveSpeed > pev->maxspeed) { + m_moveSpeed = pev->maxspeed; + } m_lastUsedNodesTime = game.time (); // special movement for swimming here @@ -1083,8 +1092,10 @@ bool Bot::updateNavigation () { m_approachingLadderTimer.start (m_frameInterval * 4.0f); } - if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !isDucking ()) { - m_moveSpeed = ladderDistance; + if (!isOnLadder () && isOnFloor () && !isDucking ()) { + if (!isPreviousLadder ()) { + m_moveSpeed = ladderDistance; + } if (m_moveSpeed < 150.0f) { m_moveSpeed = 150.0f; @@ -1107,17 +1118,14 @@ bool Bot::updateNavigation () { bool foundGround = false; - if (cr::abs (pev->origin.z - client.ent->v.origin.z) > 15.0f) { - if (isPreviousLadder ()) { - if (client.ent->v.origin.distanceSq (pev->origin) < cr::sqrf (64.0f)) { - foundGround = true; - } - } + if (!isPreviousLadder () + && client.ent->v.origin.distanceSq (pev->origin) < cr::sqrf (128.0f) + && cr::abs (client.ent->v.origin.z - pev->origin.z) > 17.0f) { + foundGround = true; } if (foundGround) { - clearSearchNodes (); - findNextBestNode (); + changeNodeIndex (prevNodeIndex); } } } @@ -1140,8 +1148,11 @@ bool Bot::updateNavigation () { game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr); if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && util.isDoorEntity (tr.pHit)) { + const auto &origin = game.getEntityOrigin (tr.pHit); + const float distanceSq = pev->origin.distanceSq (origin); + // if the door is near enough... - if (pev->origin.distanceSq (game.getEntityOrigin (tr.pHit)) < cr::sqrf (50.0f)) { + if (distanceSq < cr::sqrf (56.0f)) { ignoreCollision (); // don't consider being stuck // also 'use' the door randomly @@ -1175,8 +1186,8 @@ bool Bot::updateNavigation () { } // if bot hits the door, then it opens, so wait a bit to let it open safely - if (pev->velocity.lengthSq2d () < cr::sqrf (4.0f) && m_timeDoorOpen < game.time ()) { - if (getCurrentTaskId () != Task::MoveToPosition) { + if (pev->velocity.lengthSq2d () < cr::sqrf (10.0f) && m_timeDoorOpen < game.time ()) { + if (m_timeHitDoor >= game.time ()) { startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.5f, false); } m_timeDoorOpen = game.time () + 1.0f; // retry in 1 sec until door is open @@ -1205,10 +1216,25 @@ bool Bot::updateNavigation () { else if (m_tryOpenDoor > 4) { m_tryOpenDoor = 0; - // go back to prev node, if blocked for long time - if (graph.exists (m_previousNodes[0])) { - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, m_previousNodes[0], game.time () + 3.0f, true); + if (rg.chance (50)) { + const auto prevNode = m_previousNodes[0]; + + // go back to prev node, if blocked for long time + if (graph.exists (prevNode)) { + changeNodeIndex (prevNode); + } } + else { + const auto &dirToPoint = (pev->origin - origin).normalize2d_apx (); + const auto &forwardMove = m_moveAngles.forward ().normalize2d_apx (); + + if (distanceSq < cr::sqrf (80.0f)) { + if ((dirToPoint | forwardMove) < 0.0f && !checkWallOnBehind ()) { + m_moveSpeed = -pev->maxspeed; + } + } + } + m_timeHitDoor = game.time () + 3.0f; } } } @@ -1307,7 +1333,7 @@ bool Bot::updateNavigation () { && getCurrentTaskId () != Task::EscapeFromBomb && taskTarget != kInvalidNodeIndex) { - const Vector &bombOrigin = isBombAudible (); + const auto &bombOrigin = isBombAudible (); // bot within 'hearable' bomb tick noises? if (!bombOrigin.empty ()) { @@ -1404,7 +1430,7 @@ bool Bot::updateLiftHandling () { } else if (!m_pathWalk.empty ()) { // no lift found at node if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor) && m_pathWalk.hasNext ()) { - int nextNode = m_pathWalk.next (); + const auto nextNode = m_pathWalk.next (); if (graph.exists (nextNode) && (graph[nextNode].flags & NodeFlag::Lift)) { game.testLine (m_path->origin, graph[nextNode].origin, TraceIgnore::Everything, ent (), &tr); @@ -1727,8 +1753,13 @@ bool Bot::findNextBestNode () { int busyIndex = kInvalidNodeIndex; - float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance }; - int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; + float lessDist[3] {}; + int lessIndex[3] {}; + + for (int i = 0; i < 3; ++i) { + lessDist[i] = kInfiniteDistance; + lessIndex[i] = kInvalidNodeIndex; + } const auto &origin = pev->origin + pev->velocity * m_frameInterval; const auto &bucket = graph.getNodesInBucket (origin); @@ -1785,19 +1816,10 @@ bool Bot::findNextBestNode () { const float distanceSq = pev->origin.distanceSq (path.origin); if (distanceSq < lessDist[0]) { - lessDist[2] = lessDist[1]; - lessIndex[2] = lessIndex[1]; - - lessDist[1] = lessDist[0]; - lessIndex[1] = lessIndex[0]; - lessDist[0] = distanceSq; lessIndex[0] = path.number; } else if (distanceSq < lessDist[1]) { - lessDist[2] = lessDist[1]; - lessIndex[2] = lessIndex[1]; - lessDist[1] = distanceSq; lessIndex[1] = path.number; } @@ -1888,7 +1910,7 @@ void Bot::findValidNode () { damageValue = cr::clamp (damageValue + 100, 0, kMaxDamageValue); // affect nearby connected with victim nodes - for (auto &neighbour : m_path->links) { + for (const auto &neighbour : m_path->links) { if (graph.exists (neighbour.index)) { int neighbourValue = practice.getDamage (team, neighbour.index, neighbour.index); neighbourValue = cr::clamp (neighbourValue + 100, 0, kMaxDamageValue); @@ -2019,7 +2041,7 @@ int Bot::findBombNode () { float lastDistanceSq = kInfiniteDistance; // find nearest goal node either to bomb (if "heard" or player) - for (auto &point : goals) { + for (const auto &point : goals) { const float distanceSq = bomb.distanceSq (graph[point].origin); // check if we got more close distance @@ -2211,7 +2233,7 @@ int Bot::findCoverNode (float maxDistance) { } // now get enemies neigbouring points - for (auto &link : graph[enemyIndex].links) { + for (const auto &link : graph[enemyIndex].links) { if (link.index != kInvalidNodeIndex) { enemies.push (link.index); } @@ -2228,7 +2250,7 @@ int Bot::findCoverNode (float maxDistance) { } bool neighbourVisible = false; // now check neighbour nodes for visibility - for (auto &enemy : enemies) { + for (const auto &enemy : enemies) { if (vistab.visible (enemy, path.number)) { neighbourVisible = true; break; @@ -2422,7 +2444,7 @@ bool Bot::advanceMovement () { m_campButtons = 0; - const int nextIndex = m_pathWalk.next (); + const auto nextIndex = m_pathWalk.next (); auto kills = static_cast (practice.getDamage (m_team, nextIndex, nextIndex)); // if damage done higher than one @@ -2452,7 +2474,7 @@ bool Bot::advanceMovement () { } // force terrorist bot to plant bomb - if (m_inBombZone && !m_hasProgressBar && m_hasC4 && getCurrentTaskId () != Task::PlantBomb) { + if (m_inBombZone && !m_hasProgressBar && m_hasC4 && tid != Task::PlantBomb) { changeNextGoal (); return false; } @@ -2462,7 +2484,7 @@ bool Bot::advanceMovement () { if (!m_pathWalk.empty ()) { m_jumpSequence = false; - const int destIndex = m_pathWalk.first (); + const auto destIndex = m_pathWalk.first (); bool isCurrentJump = false; // find out about connection flags @@ -3158,13 +3180,13 @@ int Bot::getRandomCampDir () { } void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) { - const Vector &los = (moveDir - pev->origin).normalize2d_apx (); + const auto &los = (moveDir - pev->origin).normalize2d_apx (); const float dot = los | pev->angles.forward ().get2d (); if (dot > 0.0f && !checkWallOnRight ()) { m_strafeSpeed = strafeSpeed; } - else if (!checkWallOnLeft ()) { + else if (dot < 0.0f && !checkWallOnLeft ()) { m_strafeSpeed = -strafeSpeed; } } @@ -3371,15 +3393,10 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) { fprintf (stderr, "%s source path is same as dest (%d).\n", __func__, destIndex); return; } - clearSearchNodes (); - - m_chosenGoalIndex = srcIndex; - m_goalValue = 0.0f; // always use shortest-path algorithm when failed sanity checks within load if (planner.isPathsCheckFailed ()) { findShortestPath (srcIndex, destIndex); - return; } @@ -3414,6 +3431,7 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) { m_planner->setG (PlannerHeuristic::gfunctionPathDist); } } + clearSearchNodes (); m_chosenGoalIndex = srcIndex; m_goalValue = 0.0f; diff --git a/src/support.cpp b/src/support.cpp index c430078..91d0e81 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -222,7 +222,12 @@ bool BotSupport::isDoorEntity (edict_t *ent) { if (game.isNullEntity (ent)) { return false; } - return ent->v.classname.str ().startsWith ("func_door"); + const auto classHash = ent->v.classname.str ().hash (); + + constexpr auto kFuncDoor = StringRef::fnv1a32 ("func_door"); + constexpr auto kFuncDoorRotating = StringRef::fnv1a32 ("func_door_rotating"); + + return classHash == kFuncDoor || classHash == kFuncDoorRotating; } bool BotSupport::isHostageEntity (edict_t *ent) { @@ -241,6 +246,14 @@ bool BotSupport::isShootableBreakable (edict_t *ent) { if (game.isNullEntity (ent) || ent == game.getStartEntity ()) { return false; } + // todo: move the breakables list into own array, and refresh them every round, since next thing is very expensive +#if 0 + StringRef material = engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "material"); + + if (material == "7") { + return false; + } +#endif const auto limit = cv_breakable_health_limit.as (); // not shootable diff --git a/src/tasks.cpp b/src/tasks.cpp index 921cacd..3407cf0 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -148,6 +148,12 @@ void Bot::normal_ () { campingAllowed = false; } + // if the bot is about to come to the camp spot, but there is already someone else camping + if (!campingAllowed && getTask ()->data == m_currentNodeIndex && getTask ()->data != kInvalidNodeIndex) { + clearSearchNodes (); + getTask ()->data = kInvalidNodeIndex; + } + if (campingAllowed) { // crouched camping here? if (m_pathFlags & NodeFlag::Crouch) { @@ -427,7 +433,7 @@ void Bot::seekCover_ () { startTask (Task::Hide, TaskPri::Hide, kInvalidNodeIndex, game.time () + rg (3.0f, 12.0f), false); // get a valid look direction - const Vector &dest = getCampDirection (m_lastEnemyOrigin); + const auto &dest = getCampDirection (m_lastEnemyOrigin); m_aimFlags |= AimFlags::Camp; m_lookAtSafe = dest; @@ -641,18 +647,18 @@ void Bot::camp_ () { // random camp dir, or prediction auto useRandomCampDirOrPredictEnemy = [&] () { - if (!m_lastEnemyOrigin.empty ()) { + if (!m_lastEnemyOrigin.empty () && util.isAlive (m_lastEnemy)) { auto pathLength = m_lastPredictLength; auto predictNode = m_lastPredictIndex; - if (pathLength > 1 && isNodeValidForPredict (predictNode)) { + if (isNodeValidForPredict (predictNode) && pathLength > 1) { m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; } else { pathLength = 0; predictNode = findAimingNode (m_lastEnemyOrigin, pathLength); - if (pathLength > 1 && isNodeValidForPredict (predictNode)) { + if (isNodeValidForPredict (predictNode) && pathLength > 1) { m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; } } @@ -681,7 +687,7 @@ void Bot::camp_ () { TraceResult tr {}; // and use real angles to check it - auto to = m_pathOrigin + dest.forward () * 500.0f; + const auto &to = m_pathOrigin + dest.forward () * 500.0f; // let's check the destination game.testLine (getEyesPos (), to, TraceIgnore::Monsters, ent (), &tr); @@ -886,7 +892,7 @@ void Bot::defuseBomb_ () { defuseRemainingTime = fullDefuseTime - game.time (); } - const Vector &bombPos = graph.getBombOrigin (); + const auto &bombPos = graph.getBombOrigin (); bool defuseError = false; // exception: bomb has been defused @@ -1001,11 +1007,11 @@ void Bot::defuseBomb_ () { botStandOrigin = pev->origin; } - const float duckLengthSq = m_entity.distanceSq (botDuckOrigin); - const float standLengthSq = m_entity.distanceSq (botStandOrigin); + const float duckDistanceSq = m_entity.distanceSq (botDuckOrigin); + const float standDistanceSq = m_entity.distanceSq (botStandOrigin); - if (duckLengthSq > cr::sqrf (75.0f) || standLengthSq > cr::sqrf (75.0f)) { - if (standLengthSq < duckLengthSq) { + if (duckDistanceSq > cr::sqrf (75.0f) || standDistanceSq > cr::sqrf (75.0f)) { + if (standDistanceSq < duckDistanceSq) { m_duckDefuse = false; // stand } else { @@ -1126,7 +1132,7 @@ void Bot::followUser_ () { int destIndex = graph.getNearest (m_targetEntity->v.origin); auto points = graph.getNearestInRadius (200.0f, m_targetEntity->v.origin); - for (auto &newIndex : points) { + for (const auto &newIndex : points) { // if node not yet used, assign it as dest if (newIndex != m_currentNodeIndex && !isOccupiedNode (newIndex)) { destIndex = newIndex; @@ -1398,10 +1404,7 @@ void Bot::escapeFromBomb_ () { pev->button |= IN_ATTACK2; } - if (!usesKnife () - && m_lastEnemyOrigin.empty () - && !(m_states & Sense::SeeingEnemy) - && !util.isAlive (m_lastEnemy)) { + if (!usesKnife () && game.isNullEntity (m_enemy) && !util.isAlive (m_lastEnemy)) { selectWeaponById (Weapon::Knife); } @@ -1457,7 +1460,6 @@ void Bot::escapeFromBomb_ () { } void Bot::shootBreakable_ () { - // breakable destroyed? if (!util.isShootableBreakable (m_breakableEntity)) { completeTask (); @@ -1471,12 +1473,12 @@ void Bot::shootBreakable_ () { m_lookAtSafe = m_breakableOrigin; // is bot facing the breakable? - if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.95f && util.isVisible (m_breakableOrigin, ent ())) { - m_aimFlags |= AimFlags::Override; - + if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.90f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; + m_aimFlags |= AimFlags::Override; + if (usesKnife ()) { selectBestWeapon (); } @@ -1501,7 +1503,7 @@ void Bot::pickupItem_ () { return; } - const Vector &dest = game.getEntityOrigin (m_pickupItem); + const auto &dest = game.getEntityOrigin (m_pickupItem); m_destOrigin = dest; m_entity = dest; @@ -1669,7 +1671,7 @@ void Bot::pickupItem_ () { } // check if hostage is with a human teammate (hack) - for (auto &client : util.getClients ()) { + for (const auto &client : util.getClients ()) { if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) && client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) { return EntitySearchResult::Continue; diff --git a/src/vision.cpp b/src/vision.cpp index 5ec1df4..f749143 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -361,9 +361,9 @@ bool Frustum::isObjectInsidePlane (const Plane &plane, const Vector ¢er, flo return plane.result + (plane.normal | point) >= 0.0f; }; - const Vector &test = plane.normal.get2d (); - const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius; - const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius; + const auto &test = plane.normal.get2d (); + const auto &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius; + const auto &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius; return isPointInsidePlane (top) || isPointInsidePlane (bottom); } @@ -403,7 +403,7 @@ void Frustum::calculate (Planes &planes, const Vector &viewAngle, const Vector & bool Frustum::check (const Planes &planes, edict_t *ent) const { constexpr auto kOffset = Vector (0.0f, 0.0f, 5.0f); - const Vector &origin = ent->v.origin - kOffset; + const auto &origin = ent->v.origin - kOffset; for (const auto &plane : planes) { if (!isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) { @@ -507,26 +507,25 @@ void Bot::setAimDirection () { auto predictNode = m_lastPredictIndex; auto isPredictedIndexApplicable = [&] () -> bool { - if (!isNodeValidForPredict (predictNode) - || pathLength >= cv_max_nodes_for_predict.as ()) { + TraceResult result {}; + game.testLine (getEyesPos (), graph[predictNode].origin + pev->view_ofs, TraceIgnore::None, ent (), &result); + if (result.flFraction < 0.5f) { + return false; + } + const float distToPredictNodeSq = graph[predictNode].origin.distanceSq (pev->origin); + + if (distToPredictNodeSq >= cr::sqrf (2048.0f)) { return false; } - if (!vistab.visible (m_currentNodeIndex, predictNode) - || !vistab.visible (m_previousNodes[0], predictNode) - || !vistab.visible (predictNode, m_currentNodeIndex)) { - + if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) { predictNode = kInvalidNodeIndex; pathLength = kInfiniteDistanceLong; return false; } - - TraceResult result {}; - game.testLine (getEyesPos (), graph[predictNode].origin + pev->view_ofs, TraceIgnore::None, ent (), &result); - - return result.flFraction >= 0.8f && graph[predictNode].origin.distanceSq (pev->origin) > cr::sqrf (256.0f); + return isNodeValidForPredict (predictNode) && pathLength < cv_max_nodes_for_predict.as (); }; if (changePredictedEnemy) {