diff --git a/inc/yapb.h b/inc/yapb.h index 5cb191d..b9d0c9e 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -737,7 +737,7 @@ private: Vector m_enemyOrigin {}; // target origin chosen for shooting Vector m_grenade {}; // calculated vector for grenades Vector m_entity {}; // origin of entities like buttons etc. - Vector m_camp {}; // aiming vector when camping. + Vector m_lookAtSafe {}; // aiming vector when camping. Vector m_desiredVelocity {}; // desired velocity for jump nodes Vector m_breakableOrigin {}; // origin of breakable @@ -754,12 +754,13 @@ private: private: int pickBestWeapon (int *vec, int count, int moneySave); int findCampingDirection (); - int findAimingNode (const Vector &to); + int findAimingNode (const Vector &to, int &pathLength); int findNearestNode (); int findBombNode (); int findCoverNode (float maxDistance); int findDefendNode (const Vector &origin); int findBestGoal (); + int findBestGoalWhenBombAction (); int findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive); int bestPrimaryCarried (); int bestSecondaryCarried (); @@ -1181,7 +1182,6 @@ extern ConVar cv_chat; extern ConVar cv_language; extern ConVar cv_show_latency; extern ConVar cv_enable_query_hook; -extern ConVar cv_whose_your_daddy; extern ConVar cv_chatter_path; extern ConVar cv_quota; extern ConVar cv_difficulty; @@ -1202,6 +1202,7 @@ extern ConVar mp_limitteams; extern ConVar mp_autoteambalance; extern ConVar mp_footsteps; extern ConVar mp_startmoney; +extern ConVar mp_c4timer; // execute client command helper template void Bot::issueCommand (const char *fmt, Args &&...args) { diff --git a/src/botlib.cpp b/src/botlib.cpp index 9c8a71f..b4889bb 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -502,14 +502,6 @@ edict_t *Bot::lookupBreakable () { } void Bot::setIdealReactionTimers (bool actual) { - - // zero out reaction times for extreme mode - if (cv_whose_your_daddy.bool_ ()) { - m_idealReactionTime = 0.05f; - m_actualReactionTime = 0.095f; - - return; - } const auto tweak = conf.getDifficultyTweaks (m_difficulty); if (actual) { @@ -1886,8 +1878,17 @@ void Bot::setConditions () { m_states &= ~Sense::HearingEnemy; } - if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { - m_aimFlags |= AimFlags::PredictPath; + if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty () && m_seeEnemyTime + 0.5f < game.time ()) { + auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); + + if (distanceToLastEnemySq < cr::square (1600.0f)) { + auto pathLength = 0; + auto nodeIndex = findAimingNode (m_lastEnemyOrigin, pathLength); + + if (graph.exists (nodeIndex) && pathLength < kMaxBucketsInsidePos * 2 && pev->origin.distanceSq (graph[nodeIndex].origin) > cr::square (384.0f)) { + m_aimFlags |= AimFlags::PredictPath; + } + } if (seesEntity (m_lastEnemyOrigin, true)) { m_aimFlags |= AimFlags::LastEnemy; @@ -1985,7 +1986,7 @@ void Bot::filterTasks () { ratio = timeHeard * 0.1f; } bool lowAmmo = isLowOnAmmo (m_currentWeapon, 0.18f); - bool sniping = m_sniperStopTime <= game.time () && lowAmmo; + bool sniping = m_sniperStopTime > game.time () && lowAmmo; if (bots.isBombPlanted () || m_isStuck || usesKnife ()) { ratio /= 3.0f; // reduce the seek cover desire if bomb is planted @@ -2080,7 +2081,7 @@ void Bot::filterTasks () { auto survive = thresholdDesire (&filter[Task::SeekCover], 40.0f, 0.0f); survive = subsumeDesire (&filter[Task::Hide], survive); - auto def = thresholdDesire (&filter[Task::Hunt], 41.0f, 0.0f); // don't allow hunting if desires 60< + auto def = thresholdDesire (&filter[Task::Hunt], 60.0f, 0.0f); // don't allow hunting if desires 60< offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff auto sub = maxDesire (offensive, def); // default normal & careful tasks against offensive actions @@ -2771,7 +2772,7 @@ void Bot::updateAimDir () { } if (flags & AimFlags::Override) { - m_lookAt = m_camp; + m_lookAt = m_lookAtSafe; } else if (flags & AimFlags::Grenade) { m_lookAt = m_throw; @@ -2802,6 +2803,9 @@ void Bot::updateAimDir () { if (m_pickupType == Pickup::Hostage) { m_lookAt.z += 48.0f; } + else if (m_pickupType == Pickup::Weapon) { + m_lookAt.z += 72.0; + } } else if (flags & AimFlags::LastEnemy) { m_lookAt = m_lastEnemyOrigin; @@ -2814,100 +2818,59 @@ void Bot::updateAimDir () { m_wantsToFire = true; } } - else { - auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); - auto maxDistanceToEnemySq = cr::square (1600.0f); - - if (distanceToLastEnemySq >= maxDistanceToEnemySq) { - m_lastEnemyOrigin = nullptr; - m_aimFlags &= ~AimFlags::LastEnemy; - } - } } else if (flags & AimFlags::PredictPath) { - TraceResult tr {}; + bool changePredictedEnemy = true; - auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); - auto maxDistanceToEnemySq = cr::square (1600.0f); + if (m_timeNextTracking > game.time () && m_trackingEdict == m_lastEnemy) { + changePredictedEnemy = false; + } - if ((distanceToLastEnemySq < maxDistanceToEnemySq || usesSniper ()) && util.isAlive (m_lastEnemy)) { - game.testLine (getEyesPos (), m_lastEnemyOrigin, TraceIgnore::Glass, pev->pContainingEntity, &tr); + if (changePredictedEnemy) { + auto pathLength = 0; + auto aimNode = findAimingNode (m_lastEnemyOrigin, pathLength); - if (tr.flFraction >= 0.2f || tr.pHit != game.getStartEntity ()) { - auto changePredictedEnemy = true; + if (graph.exists (aimNode) && pathLength < kMaxBucketsInsidePos * 2) { + m_lookAt = graph[aimNode].origin; + m_lookAtSafe = m_lookAt; - if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { - changePredictedEnemy = false; - } - - if (changePredictedEnemy) { - auto aimPoint = findAimingNode (m_lastEnemyOrigin); - - if (aimPoint != kInvalidNodeIndex) { - m_lookAt = graph[aimPoint].origin + pev->view_ofs; - m_camp = m_lookAt; - - m_timeNextTracking = game.time () + 0.5f; - m_trackingEdict = m_lastEnemy; - - if (!usesSniper () && lastEnemyShootable ()) { - m_wantsToFire = true; - } - } - else { - m_lookAt = m_camp; - } - } - else { - m_lookAt = m_camp; - } + m_timeNextTracking = game.time () + 0.5f; + m_trackingEdict = m_lastEnemy; } else { - m_aimFlags &= ~AimFlags::PredictPath; // forget enemy far away - - if (distanceToLastEnemySq >= maxDistanceToEnemySq) { - m_lastEnemyOrigin = nullptr; + if (!m_lookAtSafe.empty ()) { + m_lookAt = m_lookAtSafe; } } } else { - m_aimFlags &= ~AimFlags::PredictPath; // forget enemy far away - - if (distanceToLastEnemySq >= maxDistanceToEnemySq) { - m_lastEnemyOrigin = nullptr; - } + m_lookAt = m_lookAtSafe; } } else if (flags & AimFlags::Camp) { - m_lookAt = m_camp; + m_lookAt = m_lookAtSafe; } else if (flags & AimFlags::Nav) { m_lookAt = m_destOrigin; - auto smoothView = [&] (int32 index) -> Vector { - auto radius = graph[index].radius; - - if (radius > 0.0f) { - return Vector (pev->angles.x, cr::wrapAngle (pev->angles.y + rg.get (-90.0f, 90.0f)), 0.0f).forward () * rg.get (2.0f, 4.0f); - } - return nullptr; - }; - - if (m_moveToGoal && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::square (160.0f)) { + if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::square (240.0f)) { auto nextPathIndex = m_pathWalk.next (); if (graph.isVisible (m_currentNodeIndex, nextPathIndex)) { - m_lookAt = graph[nextPathIndex].origin + pev->view_ofs + smoothView (nextPathIndex); + m_lookAt = graph[nextPathIndex].origin; } else { m_lookAt = m_destOrigin; } } + else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()){ + m_lookAt = m_lastEnemyOrigin;; + } else { m_lookAt = m_destOrigin; } - if (m_canChooseAimDirection && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & NodeFlag::Ladder)) { + if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & NodeFlag::Ladder)) { auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex); if (graph.exists (dangerIndex) && graph.isVisible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) { @@ -2915,13 +2878,18 @@ void Bot::updateAimDir () { m_lookAt = m_destOrigin; } else { - m_lookAt = graph[dangerIndex].origin + pev->view_ofs + smoothView (dangerIndex); + m_lookAt = graph[dangerIndex].origin + pev->view_ofs; // add danger flags m_aimFlags |= AimFlags::Danger; } } } + + // aloways use our z + if (m_lookAt == m_destOrigin) { + m_lookAt.z = getEyesPos ().z; + } } if (m_lookAt.empty ()) { @@ -3028,12 +2996,6 @@ void Bot::frame () { kick (); return; } - - // clear enemy far away - if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::square (1600.0f)) { - m_lastEnemy = nullptr; - m_lastEnemyOrigin = nullptr; - } m_slowFrameTimestamp = game.time () + 0.5f; } @@ -3227,7 +3189,7 @@ void Bot::normal_ () { m_timeCamping = game.time () + rg.get (cv_camping_time_min.float_ (), cv_camping_time_max.float_ ()); startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, m_timeCamping, true); - m_camp = m_path->origin + m_path->start.forward () * 500.0f; + m_lookAtSafe = m_path->origin + m_path->start.forward () * 500.0f; m_aimFlags |= AimFlags::Camp; m_campDirection = 0; @@ -3507,7 +3469,7 @@ void Bot::seekCover_ () { getCampDirection (&dest); m_aimFlags |= AimFlags::Camp; - m_camp = dest; + m_lookAtSafe = dest; m_campDirection = 0; // chosen waypoint is a camp waypoint? @@ -3609,7 +3571,7 @@ void Bot::pause_ () { if (m_moveSpeed < -pev->maxspeed) { m_moveSpeed = -pev->maxspeed; } - m_camp = getEyesPos () + pev->v_angle.forward () * 500.0f; + m_lookAtSafe = getEyesPos () + pev->v_angle.forward () * 500.0f; m_aimFlags |= AimFlags::Override; m_wantsToFire = true; @@ -3757,22 +3719,23 @@ void Bot::camp_ () { } if (--numFoundPoints >= 0) { - m_camp = graph[campPoints[rg.get (0, numFoundPoints)]].origin; + m_lookAtSafe = graph[campPoints[rg.get (0, numFoundPoints)]].origin; } else { - m_camp = graph[findCampingDirection ()].origin; + m_lookAtSafe = graph[findCampingDirection ()].origin; } } else { if ((!game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) || util.isAlive (m_lastEnemy)) { - auto lastEnemyNearestIndex = findAimingNode (m_lastEnemyOrigin); + auto pathLength = 0; + auto lastEnemyNearestIndex = findAimingNode (m_lastEnemyOrigin, pathLength); - if (lastEnemyNearestIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, lastEnemyNearestIndex)) { - m_camp = graph[lastEnemyNearestIndex].origin; + if (pathLength > 0 && graph.exists (lastEnemyNearestIndex)) { + m_lookAtSafe = graph[lastEnemyNearestIndex].origin; } } else { - m_camp = graph[findCampingDirection ()].origin; + m_lookAtSafe = graph[findCampingDirection ()].origin; } } } @@ -4551,7 +4514,7 @@ void Bot::shootBreakable_ () { m_checkTerrain = false; m_moveToGoal = false; m_navTimeset = game.time (); - m_camp = m_breakableOrigin; + m_lookAtSafe = m_breakableOrigin; // is bot facing the breakable? if (util.getShootingCone (ent (), m_breakableOrigin) >= 0.90f) { diff --git a/src/combat.cpp b/src/combat.cpp index 4905d26..fce8d45 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -197,7 +197,7 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) { return false; } - if (cv_whose_your_daddy.bool_ () && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) { + if (m_difficulty == Difficulty::Expert && m_kpdRatio < 1.5f && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) { ignoreFOV = true; } @@ -276,7 +276,7 @@ bool Bot::lookupEnemies () { float scaleFactor = (1.0f / calculateScaleFactor (intresting)); float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor; - if (distance * 0.7f < nearestDistance) { + if (distance < nearestDistance) { nearestDistance = distance; newEnemy = intresting; } @@ -297,7 +297,7 @@ bool Bot::lookupEnemies () { } // extra skill player can see thru smoke... if beeing attacked - if ((player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance && cv_whose_your_daddy.bool_ ()) { + if ((player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance && m_difficulty == Difficulty::Expert) { nearestDistance = cr::square (m_maxViewDistance); } @@ -309,7 +309,7 @@ bool Bot::lookupEnemies () { } float distance = player->v.origin.distanceSq (pev->origin); - if (distance * 0.7f < nearestDistance) { + if (distance < nearestDistance) { nearestDistance = distance; newEnemy = player; @@ -350,7 +350,7 @@ bool Bot::lookupEnemies () { } m_targetEntity = nullptr; // stop following when we see an enemy... - if (cv_whose_your_daddy.bool_ ()) { + if (m_difficulty == Difficulty::Expert) { m_enemySurpriseTime = m_actualReactionTime * 0.5f; } else { @@ -403,7 +403,7 @@ bool Bot::lookupEnemies () { // shoot at dying players if no new enemy to give some more human-like illusion if (m_seeEnemyTime + 0.1f > game.time ()) { if (!usesSniper ()) { - m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.45f, 0.60f); + m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.25f, 0.45f); m_actualReactionTime = 0.0f; m_states |= Sense::SuspectEnemy; @@ -493,10 +493,13 @@ const Vector &Bot::getEnemyBodyOffset () { else if (distance < 800.0f && usesSniper ()) { m_enemyParts &= ~Visibility::Head; } + else if (distance < kSprayDistance / 2 && !usesPistol ()) { + m_enemyParts &= ~Visibility::Head; + } Vector aimPos = m_enemy->v.origin; if (m_difficulty > Difficulty::Normal) { - aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.25f); + aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 2.0f); } // if we only suspect an enemy behind a wall take the worst skill @@ -504,12 +507,12 @@ const Vector &Bot::getEnemyBodyOffset () { aimPos += getBodyOffsetError (distance); } else if (util.isPlayer (m_enemy)) { - const float highOffset = cv_whose_your_daddy.bool_ () ? 1.5f : 0.0f; + const float highOffset = m_kpdRatio < 1.0f ? 1.5f : 0.0f; // now take in account different parts of enemy body if (m_enemyParts & (Visibility::Head | Visibility::Body)) { auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; - auto onLoosingStreak = m_fearLevel > m_agressionLevel || m_kpdRatio < 1.5f; + auto onLoosingStreak = (m_fearLevel > m_agressionLevel || m_kpdRatio < 1.25f); // reduce headshot percent in case we're play too good if (!onLoosingStreak) { @@ -518,7 +521,7 @@ const Vector &Bot::getEnemyBodyOffset () { // now check is our skill match to aim at head, else aim at enemy body if (rg.chance (headshotPct)) { - if (onLoosingStreak || cv_whose_your_daddy.bool_ ()) { + if (onLoosingStreak) { aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); } else { @@ -556,15 +559,15 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { }; static float offsetRanges[9][3] = { - { 0.0f, 0.0f, 0.0f }, // none - { 0.0f, 0.0f, 0.0f }, // melee - { 2.5f, 1.5f, 0.2f }, // pistol - { 6.5f, 0.0f, -9.9f }, // shotgun - { 0.5f, -6.5f, -9.0f }, // zoomrifle - { 0.5f, -6.5f, -9.5f }, // rifle - { 2.5f, 0.5f, -4.5f }, // smg - { 0.5f, 0.5f, 1.5f }, // sniper - { 1.5f, -2.0f, -9.0f } // heavy + { 0.0f, 0.0f, 0.0f }, // none + { 0.0f, 0.0f, 0.0f }, // melee + { 1.5f, 1.5f, -1.2f }, // pistol + { 6.5f, 0.0f, -9.9f }, // shotgun + { 0.5f, -8.5f, -11.0f }, // zoomrifle + { 0.5f, -8.5f, -11.5f }, // rifle + { 2.5f, 0.5f, -4.5f }, // smg + { 0.5f, 0.5f, 1.5f }, // sniper + { 1.5f, -2.0f, -12.0f } // heavy }; // only highskilled bots do that @@ -769,16 +772,16 @@ bool Bot::needToPauseFiring (float distance) { return true; } } - float offset = 5.0f; + float offset = 4.25f; - if (distance < kSprayDistance * 0.5f) { + if (distance < kSprayDistance / 4) { return false; } else if (distance < kSprayDistance) { - offset = 12.0f; + offset = 10.0f; } else if (distance < kDoubleSprayDistance) { - offset = 10.0f; + offset = 8.0f; } const float xPunch = cr::deg2rad (pev->punchangle.x); const float yPunch = cr::deg2rad (pev->punchangle.y); @@ -1117,142 +1120,142 @@ void Bot::attackMovement () { if (game.isNullEntity (m_enemy)) { return; } - float distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum? - if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.time ()) { - int approach; + if (m_lastUsedNodesTime - getFrameInterval () > game.time ()) { + return; + } - if (usesKnife ()) { - approach = 100; - } - else if ((m_states & Sense::SuspectEnemy) && !(m_states & Sense::SeeingEnemy)) { + auto approach = 0; + auto distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum? + + if (usesKnife ()) { + approach = 100; + } + else if ((m_states & Sense::SuspectEnemy) && !(m_states & Sense::SeeingEnemy)) { + approach = 49; + } + else if (m_isReloading || m_isVIP) { + approach = 29; + } + else { + approach = static_cast (m_healthValue * m_agressionLevel); + + if (usesSniper () && approach > 49) { approach = 49; } - else if (m_isReloading || m_isVIP) { - approach = 29; - } - else { - approach = static_cast (m_healthValue * m_agressionLevel); + } - if (usesSniper () && approach > 49) { - approach = 49; - } - } + // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP + 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; + } + else { + m_moveSpeed = pev->maxspeed; + } - // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP - 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; - } - else { - m_moveSpeed = pev->maxspeed; - } - - if (distance < 96.0f && !usesKnife ()) { - m_moveSpeed = -pev->maxspeed; - } + if (distance < 96.0f && !usesKnife ()) { + m_moveSpeed = -pev->maxspeed; + } + if (m_lastFightStyleCheck + 3.0f < game.time ()) { if (usesSniper ()) { m_fightStyle = Fight::Stay; - m_lastFightStyleCheck = game.time (); } - else if (usesRifle () || usesSubmachine ()) { - if (m_lastFightStyleCheck + 3.0f < game.time ()) { - int rand = rg.get (1, 100); + else if (usesRifle () || usesSubmachine () || usesHeavy ()) { + int rand = rg.get (1, 100); - if (distance < 500.0f) { + if (distance < 500.0f) { + m_fightStyle = Fight::Strafe; + } + else if (distance < 1024.0f) { + if (rand < (usesSubmachine () ? 50 : 30)) { m_fightStyle = Fight::Strafe; } - else if (distance < 1024.0f) { - if (rand < (usesSubmachine () ? 50 : 30)) { - m_fightStyle = Fight::Strafe; - } - else { - m_fightStyle = Fight::Stay; - } - } else { - if (rand < (usesSubmachine () ? 80 : 90)) { - m_fightStyle = Fight::Stay; - } - else { - m_fightStyle = Fight::Strafe; - } - } - m_lastFightStyleCheck = game.time (); - } - } - else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 100)) { - m_fightStyle = Fight::Strafe; - } - - if (isInViewCone (m_enemy->v.origin) && usesKnife ()) { - m_fightStyle = Fight::Strafe; - } - - if ((m_difficulty >= Difficulty::Normal && m_fightStyle == Fight::Strafe) || ((pev->button & IN_RELOAD) || m_isReloading) || (usesPistol () && distance < 768.0f) || usesKnife ()) { - if (m_strafeSetTime < game.time ()) { - - // to start strafing, we have to first figure out if the target is on the left side or right side - const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); - const auto &rightSide = m_enemy->v.v_angle.right ().normalize2d (); - - if ((dirToPoint | rightSide) < 0) { - m_combatStrafeDir = Dodge::Left; - } - else { - m_combatStrafeDir = Dodge::Right; - } - - if (rg.chance (30)) { - m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); - } - m_strafeSetTime = game.time () + rg.get (1.0f, 3.0f); - } - - if (m_combatStrafeDir == Dodge::Right) { - if (!checkWallOnLeft ()) { - m_strafeSpeed = -pev->maxspeed; - } - else { - m_combatStrafeDir = Dodge::Left; - m_strafeSetTime = game.time () + rg.get (1.0f, 1.5f); + m_fightStyle = Fight::Stay; } } else { - if (!checkWallOnRight ()) { - m_strafeSpeed = pev->maxspeed; + if (rand < (usesSubmachine () ? 80 : 90)) { + m_fightStyle = Fight::Stay; } else { - m_combatStrafeDir = Dodge::Right; - m_strafeSetTime = game.time () + rg.get (1.0f, 1.5f); + m_fightStyle = Fight::Strafe; } } + } + else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75)) { + m_fightStyle = Fight::Strafe; + } + else { + m_fightStyle = Fight::Stay; + } + m_lastFightStyleCheck = game.time (); + } - if (m_difficulty >= Difficulty::Hard && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.get (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 150.0f) && !usesSniper ()) { - pev->button |= IN_JUMP; + if (m_fightStyle == Fight::Strafe) { + if (m_strafeSetTime < game.time ()) { + + // to start strafing, we have to first figure out if the target is on the left side or right side + const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); + const auto &rightSide = m_enemy->v.v_angle.right ().normalize2d (); + + if ((dirToPoint | rightSide) < 0) { + m_combatStrafeDir = Dodge::Left; + } + else { + m_combatStrafeDir = Dodge::Right; + } + + if (rg.chance (30)) { + m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); + } + m_strafeSetTime = game.time () + rg.get (1.2f, 2.4f); + } + + if (m_combatStrafeDir == Dodge::Right) { + if (!checkWallOnLeft ()) { + m_strafeSpeed = -pev->maxspeed; + } + else { + m_combatStrafeDir = Dodge::Left; + m_strafeSetTime = game.time () + rg.get (0.8f, 1.2f); } } else { - if ((m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { - int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); - - if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { - m_duckTime = game.time () + 0.64f; - } + if (!checkWallOnRight ()) { + m_strafeSpeed = pev->maxspeed; + } + else { + m_combatStrafeDir = Dodge::Right; + m_strafeSetTime = game.time () + rg.get (0.8f, 1.2f); } - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - m_navTimeset = game.time (); } + + if (m_difficulty >= Difficulty::Hard && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.get (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 150.0f) && !usesSniper ()) { + pev->button |= IN_JUMP; + } + } + else { + if ((m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { + int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); + + if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { + m_duckTime = game.time () + 0.64f; + } + } + m_moveSpeed = 0.0f; + m_strafeSpeed = 0.0f; + m_navTimeset = game.time (); } if (m_difficulty >= Difficulty::Hard && isOnFloor () && (m_duckTime < game.time ())) { if (distance < 768.0f) { - 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 (7, 12) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { pev->button |= IN_JUMP; } } diff --git a/src/manager.cpp b/src/manager.cpp index cc3a9a8..ca92c3b 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -912,10 +912,6 @@ void BotManager::updateBotDifficulties () { } void BotManager::balanceBotDifficulties () { - // with nightmare difficulty, there is no balance - if (cv_whose_your_daddy.bool_ ()) { - return; - } // difficulty chaning once per round (time) auto updateDifficulty = [] (Bot *bot, int32 offset) { bot->m_difficulty = cr::clamp (static_cast (bot->m_difficulty + offset), Difficulty::Noob, Difficulty::Expert); diff --git a/src/navigate.cpp b/src/navigate.cpp index 0664fca..e94e141 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -7,42 +7,22 @@ #include -ConVar cv_whose_your_daddy ("yb_whose_your_daddy", "0", "Enables or disables extra hard difficulty for bots."); ConVar cv_path_heuristic_mode ("yb_path_heuristic_mode", "4", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f); - ConVar cv_path_danger_factor_min ("yb_path_danger_factor_min", "200", "Lower bound of danger factor that used to add additional danger to path based on practice.", true, 100.0f, 2400.0f); ConVar cv_path_danger_factor_max ("yb_path_danger_factor_max", "400", "Upper bound of danger factor that used to add additional danger to path based on practice.", true, 200.0f, 4800.0f); int Bot::findBestGoal () { - auto pushToHistroy = [&] (int32 goal) -> int32 { m_goalHistory.push (goal); return goal; }; // chooses a destination (goal) node for a bot - if (!bots.isBombPlanted () && m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { - int result = kInvalidNodeIndex; + if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { + auto result = findBestGoalWhenBombAction (); - game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { - if (util.isModel (ent, "backpack.mdl")) { - result = graph.getNearest (game.getEntityOrigin (ent)); - - if (graph.exists (result)) { - return EntitySearchResult::Break; - } - } - return EntitySearchResult::Continue; - }); - - // found one ? if (graph.exists (result)) { - return m_loosedBombNodeIndex = result; - } - - // forcing terrorist bot to not move to another bomb spot - if (m_inBombZone && !m_hasProgressBar && m_hasC4) { - return graph.getNearest (pev->origin, 768.0f, NodeFlag::Goal); + return result; } } int tactic = 0; @@ -165,6 +145,68 @@ int Bot::findBestGoal () { return pushToHistroy (findGoalPost (tactic, defensiveNodes, offensiveNodes)); } +int Bot::findBestGoalWhenBombAction () { + int result = kInvalidNodeIndex; + + if (!bots.isBombPlanted ()) { + game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { + if (util.isModel (ent, "backpack.mdl")) { + result = graph.getNearest (game.getEntityOrigin (ent)); + + if (graph.exists (result)) { + return EntitySearchResult::Break; + } + } + return EntitySearchResult::Continue; + }); + + // found one ? + if (graph.exists (result)) { + return m_loosedBombNodeIndex = result; + } + + // forcing terrorist bot to not move to another bomb spot + if (m_inBombZone && !m_hasProgressBar && m_hasC4) { + return graph.getNearest (pev->origin, 1024.0f, NodeFlag::Goal); + } + } + else if (!m_defendedBomb) { + const auto &bombOrigin = graph.getBombOrigin (); + + if (!bombOrigin.empty ()) { + m_defendedBomb = true; + + result = findDefendNode (bombOrigin); + const Path &path = graph[result]; + + float bombTimer = mp_c4timer.float_ (); + float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + + if (timeMidBlowup > game.time ()) { + clearTask (Task::MoveToPosition); // remove any move tasks + + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, timeMidBlowup, true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, result, timeMidBlowup, true); // push move command + + if (path.vis.crouch <= path.vis.stand) { + m_campButtons |= IN_DUCK; + } + else { + m_campButtons &= ~IN_DUCK; + } + if (rg.chance (90)) { + pushChatterMessage (Chatter::DefendingBombsite); + } + } + else { + pushRadioMessage (Radio::ShesGonnaBlow); // issue an additional radio message + } + return result; + } + } + return result; +} + int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) { int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; @@ -349,6 +391,11 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { continue; } + // skip if it's not standing + if (client.ent->v.flags & FL_DUCKING) { + continue; + } + // our team, alive and not myself? if (client.team != m_team || client.ent == ent ()) { continue; @@ -882,7 +929,7 @@ bool Bot::updateNavigation () { } float desiredDistance = 8.0f; - float nodeDistance = pev->origin.distance (m_pathOrigin); + float nodeDistance = pev->origin.distance2d (m_pathOrigin); // initialize the radius for a special node type, where the node is considered to be reached if (m_path->flags & NodeFlag::Lift) { @@ -900,6 +947,9 @@ bool Bot::updateNavigation () { else if (m_path->number == cv_debug_goal.int_ ()) { desiredDistance = 0.0f; } + else if (isOccupiedNode (m_path->number)) { + desiredDistance = 72.0f; + } else { desiredDistance = m_path->radius; } @@ -1620,7 +1670,7 @@ void Bot::clearRoute () { m_routes.clear (); } -int Bot::findAimingNode (const Vector &to) { +int Bot::findAimingNode (const Vector &to, int &pathLength) { // return the most distant node which is seen from the bot to the target and is within count if (m_currentNodeIndex == kInvalidNodeIndex) { @@ -1633,7 +1683,6 @@ int Bot::findAimingNode (const Vector &to) { if (destIndex == kInvalidNodeIndex) { return kInvalidNodeIndex; } - const float kMaxDistance = ((m_states & Sense::HearingEnemy) || (m_states & Sense::SuspectEnemy) || m_seeEnemyTime + 3.0f > game.time ()) ? 0.0f : 512.0f; while (destIndex != m_currentNodeIndex) { destIndex = (graph.m_matrix.data () + (destIndex * graph.length ()) + m_currentNodeIndex)->index; @@ -1641,8 +1690,9 @@ int Bot::findAimingNode (const Vector &to) { if (destIndex < 0) { break; } + ++pathLength; - if (graph.isVisible (m_currentNodeIndex, destIndex) && graph.isVisible (destIndex, m_currentNodeIndex) && kMaxDistance > 0.0f && graph[destIndex].origin.distanceSq (graph[m_currentNodeIndex].origin) > cr::square (kMaxDistance)) { + if (graph.isVisible (m_currentNodeIndex, destIndex)) { bestIndex = destIndex; break; } @@ -3039,7 +3089,7 @@ void Bot::updateLookAngles () { return; } - if (m_difficulty == Difficulty::Expert && (m_aimFlags & AimFlags::Enemy) && (m_wantsToFire || usesSniper ()) && cv_whose_your_daddy.bool_ ()) { + if (m_difficulty == Difficulty::Expert && (m_aimFlags & AimFlags::Enemy) && (m_wantsToFire || usesSniper ()) && m_kpdRatio < 1.0f && m_healthValue < 50.0f) { pev->v_angle = direction; updateBodyAngles ();