diff --git a/inc/yapb.h b/inc/yapb.h index 188bdd4..9de34a7 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -132,7 +132,7 @@ public: ~PathWalk () = default; public: - int32_t &doubleNext () { + int32_t &nextX2 () { return at (2); } @@ -307,6 +307,7 @@ private: bool m_isCreature {}; // bot is not a player, but something else ? zombie ? bool m_defuseNotified {}; // bot is notified about bomb defusion bool m_jumpSequence {}; // next path link will be jump link + bool m_checkFall {}; // check bot fall PathWalk m_pathWalk {}; // pointer to current node from path Dodge m_dodgeStrafeDir {}; // direction to strafe @@ -325,6 +326,7 @@ private: edict_t *m_targetEntity {}; // the entity that the bot is trying to reach edict_t *m_avoidGrenade {}; // pointer to grenade entity to avoid edict_t *m_hindrance {}; // the hindrance + edict_t *m_hearedEnemy {}; // the heared enemy Vector m_liftTravelPos {}; // lift travel position Vector m_moveAngles {}; // bot move angles @@ -344,6 +346,7 @@ private: Vector m_desiredVelocity {}; // desired velocity for jump nodes Vector m_breakableOrigin {}; // origin of breakable Vector m_rightRef {}; // right referential vector + Vector m_checkFallPoint[2] {}; // check fall point Array m_ignoredBreakable {}; // list of ignored breakables Array m_hostages {}; // pointer to used hostage entities @@ -359,7 +362,6 @@ private: private: int pickBestWeapon (Array &vec, int moneySave); int getRandomCampDir (); - int findAimingNode (const Vector &to, int &pathLength); int findNearestNode (); int findBombNode (); int findCoverNode (float maxDistance); @@ -416,6 +418,7 @@ private: bool isOutOfBombTimer (); bool isWeaponBadAtDistance (int weaponIndex, float distance); bool needToPauseFiring (float distance); + bool checkZoom (float distance); bool lookupEnemies (); bool isEnemyHidden (edict_t *enemy); bool isEnemyInvincible (edict_t *enemy); @@ -464,6 +467,7 @@ private: void updatePickups (); void ensureEntitiesClear (); void checkTerrain (float movedDistance, const Vector &dirNormal); + void checkFall (); void checkDarkness (); void checkParachute (); void updatePracticeValue (int damage); diff --git a/src/botlib.cpp b/src/botlib.cpp index 155d92b..6143839 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -133,11 +133,11 @@ void Bot::avoidGrenades () { } } else if (cv_smoke_grenade_checks.as () == 1 && (pent->v.flags & FL_ONGROUND) && model == kSmokeModelName) { - if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov / 3.0f) { + if (seesEntity (pent->v.origin) && isInFOV (pent->v.origin - getEyesPos ()) < pev->fov / 3.0f) { const auto &entOrigin = game.getEntityOrigin (pent); const auto &betweenUs = (entOrigin - pev->origin).normalize_apx (); const auto &betweenNade = (entOrigin - pev->origin).normalize_apx (); - const auto &betweenResult = ((Vector (betweenNade.y, betweenNade.x, 0.0f) * 150.0f + entOrigin) - pev->origin).normalize_apx (); + const auto &betweenResult = ((betweenNade.get2d () * 150.0f + entOrigin) - pev->origin).normalize_apx (); if ((betweenNade | betweenUs) > (betweenNade | betweenResult) && util.isVisible (pent->v.origin, ent ())) { const float distance = entOrigin.distance (pev->origin); @@ -751,9 +751,6 @@ void Bot::updatePickups () { } void Bot::ensureEntitiesClear () { - if ((!m_isStuck || m_navTimeset + getEstimatedNodeReachTime () + m_frameInterval * 2.0f > game.time ()) && !(m_states & Sense::SeeingEnemy)) { - return; - } const auto tid = getCurrentTaskId (); if (tid == Task::PickupItem || (m_states & Sense::PickupItem)) { @@ -1685,8 +1682,10 @@ void Bot::overrideConditions () { // special handling for sniping if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) - && m_sniperStopTime > game.time () - && tid != Task::SeekCover) { + && m_shootTime - 0.4f <= game.time () + && m_sniperStopTime > game.time ()) { + + ignoreCollision (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -1897,8 +1896,7 @@ void Bot::setConditions () { } // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) - if (!cv_ignore_enemies - && m_soundUpdateTime < game.time () + if (m_soundUpdateTime < game.time () && m_blindTime < game.time () && m_seeEnemyTime + 1.0f < game.time ()) { @@ -3203,6 +3201,9 @@ void Bot::logic () { checkTerrain (movedDistance, dirNormal); } + // if we have fallen from the place of move, the nearest point is allowed + checkFall (); + // check the darkness if (cv_check_darkness) { checkDarkness (); @@ -3216,7 +3217,11 @@ void Bot::logic () { m_moveSpeed = -pev->maxspeed; m_strafeSpeed = pev->maxspeed * static_cast (m_needAvoidGrenade); } - ensureEntitiesClear (); + + // ensure we're not stuck destroying/picking something + if (m_navTimeset + getEstimatedNodeReachTime () < game.time () && !(m_states & Sense::SeeingEnemy) && m_moveToGoal) { + ensureEntitiesClear (); + } // check if need to use parachute checkParachute (); @@ -3839,10 +3844,10 @@ bool Bot::isOutOfBombTimer () { } void Bot::updateHearing () { - if (game.is (GameFlags::FreeForAll) || m_enemyIgnoreTimer > game.time ()) { + if (game.is (GameFlags::FreeForAll) || m_enemyIgnoreTimer > game.time () || cv_ignore_enemies) { return; } - edict_t *hearedEnemy = nullptr; + m_hearedEnemy = nullptr; float nearestDistanceSq = kInfiniteDistance; // setup potential visibility set from engine @@ -3870,13 +3875,13 @@ void Bot::updateHearing () { } if (distanceSq < nearestDistanceSq) { - hearedEnemy = client.ent; + m_hearedEnemy = client.ent; nearestDistanceSq = distanceSq; } } // did the bot hear someone ? - if (util.isPlayer (hearedEnemy)) { + if (util.isPlayer (m_hearedEnemy)) { // change to best weapon if heard something if (m_shootTime < game.time () - 5.0f && isOnFloor () @@ -3898,7 +3903,7 @@ void Bot::updateHearing () { auto getHeardOriginWithError = [&] () -> Vector { auto error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f; - auto origin = hearedEnemy->v.origin; + auto origin = m_hearedEnemy->v.origin; origin.x = origin.x + rg (-error, error); origin.y = origin.y + rg (-error, error); @@ -3908,13 +3913,13 @@ void Bot::updateHearing () { // didn't bot already have an enemy ? take this one... if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { - m_lastEnemy = hearedEnemy; + m_lastEnemy = m_hearedEnemy; m_lastEnemyOrigin = getHeardOriginWithError (); } // bot had an enemy, check if it's the heard one else { - if (hearedEnemy == m_lastEnemy) { + if (m_hearedEnemy == m_lastEnemy) { // bot sees enemy ? then bail out ! if (m_states & Sense::SeeingEnemy) { return; @@ -3925,8 +3930,8 @@ void Bot::updateHearing () { // if bot had an enemy but the heard one is nearer, take it instead const float distanceSq = m_lastEnemyOrigin.distanceSq (pev->origin); - if (distanceSq > hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) { - m_lastEnemy = hearedEnemy; + if (distanceSq > m_hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) { + m_lastEnemy = m_hearedEnemy; m_lastEnemyOrigin = getHeardOriginWithError (); } else { @@ -3936,9 +3941,9 @@ void Bot::updateHearing () { } // check if heard enemy can be seen - if (checkBodyParts (hearedEnemy)) { - m_enemy = hearedEnemy; - m_lastEnemy = hearedEnemy; + if (checkBodyParts (m_hearedEnemy)) { + m_enemy = m_hearedEnemy; + m_lastEnemy = m_hearedEnemy; m_lastEnemyOrigin = m_enemyOrigin; m_states |= Sense::SeeingEnemy; @@ -3948,15 +3953,15 @@ void Bot::updateHearing () { // check if heard enemy can be shoot through some obstacle else { if (cv_shoots_thru_walls - && m_lastEnemy == hearedEnemy + && m_lastEnemy == m_hearedEnemy && rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct) && m_seeEnemyTime + 3.0f > game.time () - && isPenetrableObstacle (hearedEnemy->v.origin)) { + && isPenetrableObstacle (m_hearedEnemy->v.origin)) { - m_enemy = hearedEnemy; - m_lastEnemy = hearedEnemy; - m_enemyOrigin = hearedEnemy->v.origin; - m_lastEnemyOrigin = hearedEnemy->v.origin; + m_enemy = m_hearedEnemy; + m_lastEnemy = m_hearedEnemy; + m_enemyOrigin = m_hearedEnemy->v.origin; + m_lastEnemyOrigin = m_hearedEnemy->v.origin; m_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy); m_seeEnemyTime = game.time (); diff --git a/src/combat.cpp b/src/combat.cpp index 7686570..84af927 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -876,6 +876,70 @@ bool Bot::needToPauseFiring (float distance) { return false; } +bool Bot::checkZoom (float distance) { + int zoomMagnification = 0; + bool zoomChange = false; + + // is the bot holding a sniper rifle? + if (usesSniper ()) { + // should the bot switch to the long-range zoom? + if (distance > 1500.0f) { + zoomMagnification = 2; + } + + // else should the bot switch to the close-range zoom ? + else if (distance > 150.0f) { + zoomMagnification = 1; + } + + // else should the bot restore the normal view ? + else if (distance <= 150.0f) { + zoomMagnification = 0; + } + } + + // else is the bot holding a zoomable rifle? + else if (m_difficulty < Difficulty::Hard && usesZoomableRifle ()) { + // should the bot switch to zoomed mode? + if (distance > 800.0f) { + zoomMagnification = 1; + } + + // else should the bot restore the normal view? + else if (distance <= 800.0f) { + zoomMagnification = 0; + } + } + + switch (zoomMagnification) { + case 0: + if (pev->fov < 90.0f) { + zoomChange = true; + } + break; + + case 1: + if (pev->fov >= 90.0f) { + zoomChange = true; + } + break; + + case 2: + if (pev->fov >= 40.0f) { + zoomChange = true; + } + break; + } + + if (zoomChange && m_zoomCheckTime < game.time ()) { + pev->button |= IN_ATTACK2; + m_shootTime = game.time () + 0.15f; + + m_zoomCheckTime = game.time () + 0.5f; + } + return zoomChange; +} + void Bot::selectWeapons (float distance, int index, int id, int choosen) { const auto tab = conf.getRawWeapons (); @@ -922,51 +986,23 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { m_shieldCheckTime = game.time () + 1.0f; } - // is the bot holding a sniper rifle? - if (usesSniper () && m_zoomCheckTime < game.time ()) { - // should the bot switch to the long-range zoom? - if (distance > 1500.0f && pev->fov >= 40.0f) { - pev->button |= IN_ATTACK2; - } - - // else should the bot switch to the close-range zoom ? - else if (distance > 150.0f && pev->fov >= 90.0f) { - pev->button |= IN_ATTACK2; - } - - // else should the bot restore the normal view ? - else if (distance <= 150.0f && pev->fov < 90.0f) { - pev->button |= IN_ATTACK2; - } - m_zoomCheckTime = game.time () + 0.25f; - } - - // else is the bot holding a zoomable rifle? - else if (m_difficulty < Difficulty::Hard && usesZoomableRifle () && m_zoomCheckTime < game.time ()) { - // should the bot switch to zoomed mode? - if (distance > 800.0f && pev->fov >= 90.0f) { - pev->button |= IN_ATTACK2; - } - - // else should the bot restore the normal view? - else if (distance <= 800.0f && pev->fov < 90.0f) { - pev->button |= IN_ATTACK2; - } - m_zoomCheckTime = game.time () + 0.5f; + if (checkZoom (distance)) { + return; } // we're should stand still before firing sniper weapons, else sniping is useless.. if (usesSniper () && (m_aimFlags & (AimFlags::Enemy | AimFlags::LastEnemy)) - && !m_isReloading && pev->velocity.lengthSq () > 0.0f - && getCurrentTaskId () != Task::SeekCover) { + && !m_isReloading && pev->velocity.lengthSq () > 0.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - m_navTimeset = game.time (); + if (!cr::fzero (pev->velocity.x) || !cr::fzero (pev->velocity.y) || !cr::fzero (pev->velocity.z)) { + m_moveSpeed = 0.0f; + m_strafeSpeed = 0.0f; + m_navTimeset = game.time (); - if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) { - m_sniperStopTime = game.time () + 2.0f; - return; + if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) { + m_sniperStopTime = game.time () + 2.0f; + return; + } } } @@ -1309,7 +1345,7 @@ void Bot::attackMovement () { 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 ()) + if (approach >= 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ()) && distance < pistolStrafeDistance && isInViewCone (m_enemyOrigin))) { m_fightStyle = Fight::Strafe; @@ -1401,7 +1437,7 @@ void Bot::attackMovement () { } } else if (m_fightStyle == Fight::Stay) { - const bool alreadyDucking = m_duckTime > game.time () || isDucking (); + const bool alreadyDucking = m_duckTime < game.time () || isDucking (); if (alreadyDucking) { m_duckTime = game.time () + m_frameInterval * 3.0f; @@ -1422,14 +1458,6 @@ void Bot::attackMovement () { m_strafeSpeed = 0.0f; } - if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) { - if (distance < kSprayDistanceX2) { - if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { - pev->button |= IN_JUMP; - } - } - } - if (m_isReloading) { m_moveSpeed = -pev->maxspeed; m_duckTime = game.time () - 1.0f; diff --git a/src/manager.cpp b/src/manager.cpp index 68736ee..8efd9e2 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1467,6 +1467,11 @@ void Bot::newRound () { m_timeDoorOpen = 0.0f; + for (auto &fall : m_checkFallPoint) { + fall = nullptr; + } + m_checkFall = false; + resetCollision (); resetDoubleJump (); diff --git a/src/navigate.cpp b/src/navigate.cpp index e79c9de..a9a625d 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -459,7 +459,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { } m_hindrance = nullptr; - float distanceSq = cr::sqrf (384.0f); + float distanceSq = cr::sqrf (512.0f); if (isOnLadder () || isInNarrowPlace ()) { return; @@ -489,6 +489,16 @@ void Bot::doPlayerAvoidance (const Vector &normal) { if (ownPrio > otherPrio) { continue; } + + // they are higher priority - make way, unless we're already making way for someone more important + if (!game.isNullEntity (m_hindrance) && m_hindrance != client.ent) { + const auto avoidPrio = bots.getPlayerPriority (m_hindrance); + + // ignore because we're already avoiding someone better + if (avoidPrio > otherPrio) { + continue; + } + } const auto nearestDistanceSq = client.ent->v.origin.distanceSq (pev->origin); if (nearestDistanceSq < cr::sqrf (pev->maxspeed) && nearestDistanceSq < distanceSq) { @@ -815,6 +825,59 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } } +void Bot::checkFall () { + + if (!m_checkFall) { + if (isOnFloor ()) { + m_checkFallPoint[0] = pev->origin; + + if (!game.isNullEntity (m_enemy)) { + m_checkFallPoint[1] = game.getEntityOrigin (m_enemy); + } + else if (m_currentNodeIndex != kInvalidNodeIndex) { + m_checkFallPoint[1] = m_pathOrigin; + } + else { + m_checkFallPoint[1] = nullptr; + } + } + else if (!isOnLadder () && !isInWater ()) { + if (!m_checkFallPoint[0].empty () && !m_checkFallPoint[1].empty ()) { + m_checkFall = true; + } + } + } + + if (!m_checkFall || !isOnFloor ()) { + return; + } + m_checkFall = false; + bool fixFall = false; + + const float baseDistanceSq = m_checkFallPoint[0].distanceSq (m_checkFallPoint[1]); + const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]); + + if (nowDistanceSq > baseDistanceSq + && (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f) + && 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) { + fixFall = true; + } + + if (m_currentNodeIndex != kInvalidNodeIndex) { + if (pev->origin.distanceSq (m_checkFallPoint[1]) <= cr::sqrf (32.0f) && pev->origin.z + 16.0f < m_checkFallPoint[1].z) { + fixFall = true; + } + } + + if (fixFall) { + m_currentNodeIndex = kInvalidNodeIndex; + findValidNode (); + } +} + void Bot::moveToGoal () { findValidNode (); @@ -1607,33 +1670,6 @@ void Bot::clearSearchNodes () { m_chosenGoalIndex = kInvalidNodeIndex; } -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 - ensureCurrentNodeIndex (); - - const int destIndex = graph.getNearest (to); - int bestIndex = m_currentNodeIndex; - - if (destIndex == kInvalidNodeIndex) { - return kInvalidNodeIndex; - } - - auto result = planner.find (destIndex, m_currentNodeIndex, [&] (int index) { - ++pathLength; - - if (vistab.visible (m_currentNodeIndex, index)) { - bestIndex = index; - return false; - } - return true; - }); - - if (result && bestIndex == m_currentNodeIndex) { - return kInvalidNodeIndex; - } - return bestIndex; -} - bool Bot::findNextBestNode () { // this function find a node in the near of the bot if bot had lost his path of pathfinder needs // to be restarted over again. @@ -1641,7 +1677,7 @@ bool Bot::findNextBestNode () { int busyIndex = kInvalidNodeIndex; float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance }; - int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex , kInvalidNodeIndex }; + int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; const auto &origin = pev->origin + pev->velocity * m_frameInterval; const auto &bucket = graph.getNodesInBucket (origin); @@ -3093,9 +3129,9 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) { if (needZeroVelocity && client.ent->v.velocity.length2d () > 0.0f) { continue; } - const auto length = client.origin.distanceSq (graph[index].origin); + const auto distanceSq = client.origin.distanceSq (graph[index].origin); - if (length < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (90.0f), cr::sqrf (120.0f))) { + if (distanceSq < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (90.0f), cr::sqrf (120.0f))) { return true; } auto bot = bots[client.ent]; @@ -3103,7 +3139,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) { if (bot == nullptr || bot == this || !bot->m_isAlive) { continue; } - return bot->m_currentNodeIndex == index; + return bot->m_currentNodeIndex == index || bot->m_previousNodes[0] == index; } return false; } diff --git a/src/tasks.cpp b/src/tasks.cpp index 2b3d25f..98cb5eb 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -212,7 +212,7 @@ void Bot::normal_ () { else if (m_team == Team::CT) { if (!bots.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) { const int index = findDefendNode (m_path->origin); - float campTime = rg (25.0f, 40.f); + float campTime = rg (25.0f, 40.0f); // rusher bots don't like to camp too much if (m_personality == Personality::Rusher) { @@ -271,7 +271,7 @@ void Bot::normal_ () { if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed && mp_footsteps) && m_difficulty >= Difficulty::Normal && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy)) - && pev->origin.distanceSq (m_lastEnemyOrigin) < cr::sqrf (768.0f) + && numEnemiesNear (pev->origin, 768.0f) >= 1 && !isKnifeMode () && !bots.isBombPlanted ()) { @@ -624,12 +624,12 @@ void Bot::camp_ () { // random camp dir, or prediction auto useRandomCampDirOrPredictEnemy = [&] () { - if (game.isNullEntity (m_lastEnemy) || !m_lastEnemyOrigin.empty ()) { - auto pathLength = 0; - auto lastEnemyNearestIndex = findAimingNode (m_lastEnemyOrigin, pathLength); + if (!m_lastEnemyOrigin.empty ()) { + auto pathLength = m_lastPredictLength; + auto predictNode = m_lastPredictIndex; - if (pathLength > 1 && graph.exists (lastEnemyNearestIndex)) { - m_lookAtSafe = graph[lastEnemyNearestIndex].origin; + if (pathLength > 1 && graph.exists (predictNode)) { + m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; } } else { @@ -638,10 +638,8 @@ void Bot::camp_ () { }; if (m_nextCampDirTime < game.time ()) { - m_nextCampDirTime = game.time () + rg (2.0f, 5.0f); - if (m_pathFlags & NodeFlag::Camp) { - Vector dest; + Vector dest {}; // switch from 1 direction to the other if (m_campDirection < 1) { @@ -674,6 +672,7 @@ void Bot::camp_ () { else { useRandomCampDirOrPredictEnemy (); } + m_nextCampDirTime = game.time () + rg (1.0f, 4.0f); } // press remembered crouch button pev->button |= m_campButtons;