diff --git a/inc/constant.h b/inc/constant.h index 065ea10..01bc953 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -430,7 +430,7 @@ constexpr auto kGrenadeCheckTime = 0.6f; constexpr auto kSprayDistance = 360.0f; constexpr auto kSprayDistanceX2 = kSprayDistance * 2; constexpr auto kMaxChatterRepeatInterval = 99.0f; -constexpr auto kViewFrameUpdate = 1.0f / 30.0f; +constexpr auto kViewFrameUpdate = 1.0f / 25.0f; constexpr auto kGrenadeDamageRadius = 385.0f; constexpr auto kInfiniteDistanceLong = static_cast (kInfiniteDistance); diff --git a/inc/storage.h b/inc/storage.h index 20f7bcb..7093cd0 100644 --- a/inc/storage.h +++ b/inc/storage.h @@ -29,7 +29,7 @@ CR_DECLARE_SCOPED_ENUM (StorageOption, CR_DECLARE_SCOPED_ENUM (StorageVersion, Graph = 2, Practice = 2, - Vistable = 3, + Vistable = 4, Matrix = 2, Podbot = 7 ) diff --git a/inc/yapb.h b/inc/yapb.h index 8f4f67b..0f24ec6 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -109,6 +109,12 @@ struct Client { ClientNoise noise; }; +// think delay mapping +struct FrameDelay { + float interval {}; + float time {}; +}; + // include bot graph stuff #include #include @@ -235,7 +241,7 @@ private: float m_knifeAttackTime {}; // time to rush with knife (at the beginning of the round) float m_duckDefuseCheckTime {}; // time to check for ducking for defuse float m_frameInterval {}; // bot's frame interval - float m_lastCommandTime {}; // time bot last thinked + float m_previousThinkTime {}; // time bot last thinked float m_reloadCheckTime {}; // time to check reloading float m_zoomCheckTime {}; // time to check zoom again float m_shieldCheckTime {}; // time to check shield drawing again @@ -345,7 +351,9 @@ private: Path *m_path {}; // pointer to the current path node String m_chatBuffer {}; // space for strings (say text...) Frustum::Planes m_viewFrustum {}; + CountdownTimer m_forgetLastVictimTimer {}; // time to forget last victim position ? + CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder private: int pickBestWeapon (Array &vec, int moneySave); @@ -589,8 +597,6 @@ public: float m_agressionLevel {}; // dynamic aggression level (in game) float m_fearLevel {}; // dynamic fear level (in game) float m_nextEmotionUpdate {}; // next time to sanitize emotions - float m_updateTime {}; // skip some frames in bot thinking - float m_updateInterval {}; // interval between frames float m_goalValue {}; // ranking value for this node float m_viewDistance {}; // current view distance float m_maxViewDistance {}; // maximum view distance @@ -675,9 +681,13 @@ public: BurstMode m_weaponBurstMode {}; // bot using burst mode? (famas/glock18, but also silencer mode) Personality m_personality {}; // bots type Array m_tasks {}; + Deque m_msgQueue {}; Array m_goalHist {}; + FrameDelay m_thinkDelay {}; + FrameDelay m_commandDelay {}; + public: Bot (edict_t *bot, int difficulty, int personality, int team, int skin); diff --git a/src/botlib.cpp b/src/botlib.cpp index 70dd96b..61e6880 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -1789,11 +1789,7 @@ void Bot::refreshEnemyPredict () { && m_shootTime + 1.5f > game.time (); if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) { - const auto weaponPenetratePower = conf.findWeaponById (m_currentWeapon).penetratePower; - - if (isPenetrableObstacle3 (m_lastEnemyOrigin, weaponPenetratePower)) { - m_aimFlags |= AimFlags::LastEnemy; - } + m_aimFlags |= AimFlags::LastEnemy; } } @@ -1804,7 +1800,7 @@ void Bot::refreshEnemyPredict () { void Bot::setLastVictim (edict_t *ent) { m_lastVictim = ent; - m_lastVictimOrigin = ent->v.origin + ent->v.view_ofs; + m_lastVictimOrigin = ent->v.origin; m_forgetLastVictimTimer.start (rg.get (1.0f, 2.0f)); } @@ -2000,7 +1996,7 @@ void Bot::filterTasks () { float timeHeard = m_heardSoundTime - game.time (); float ratio = 0.0f; - m_retreatTime = game.time () + rg.get (3.0f, 15.0f); + m_retreatTime = game.time () + rg.get (1.0f, 4.0f); if (timeSeen > timeHeard) { timeSeen += 10.0f; @@ -2836,11 +2832,18 @@ void Bot::checkParachute () { void Bot::frame () { pev->flags |= FL_CLIENT | FL_FAKECLIENT; // restore fake client bit, just in case - if (m_updateTime <= game.time ()) { + if (m_thinkDelay.time <= game.time ()) { update (); - } - else if (m_isAlive) { - updateLookAngles (); + + + // delay next execution for thinking + m_thinkDelay.time = game.time () + m_thinkDelay.interval; + + // run bot command on twice speed + if (m_commandDelay.time <= game.time ()) { + runMovement (); + m_commandDelay.time = game.time () + m_commandDelay.interval; + } } if (m_slowFrameTimestamp > game.time ()) { @@ -2955,10 +2958,6 @@ void Bot::update () { else if (!botMovement) { resetMovement (); } - runMovement (); - - // delay next execution - m_updateTime = game.time () + m_updateInterval; } void Bot::logicDuringFreezetime () { @@ -2966,6 +2965,12 @@ void Bot::logicDuringFreezetime () { return; } + // simply skip randomly + if (rg.chance (10)) { + return; + } + pev->button &= ~IN_DUCK; + if (rg.chance (15) && m_jumpTime + rg.get (1.0, 2.0f) < game.time ()) { pev->button |= IN_JUMP; m_jumpTime = game.time (); @@ -3215,9 +3220,7 @@ void Bot::logic () { m_moveSpeed = -pev->maxspeed; m_strafeSpeed = pev->maxspeed * static_cast (m_needAvoidGrenade); } - ensureEntitiesClear (); - translateInput (); // check if need to use parachute checkParachute (); @@ -3742,13 +3745,13 @@ bool Bot::canRunHeavyWeight () { uint8_t Bot::computeMsec () { // estimate msec to use for this command based on time passed from the previous command - return static_cast (cr::min (static_cast (cr::roundf ((game.time () - m_lastCommandTime) * 1000.0f)), 255)); + return static_cast (cr::min (static_cast (cr::roundf ((game.time () - m_previousThinkTime) * 1000.0f)), 255)); } const Vector &Bot::getRpmAngles () { // get angles to pass to run player move function - if ((m_pathFlags & NodeFlag::Ladder) || getCurrentTaskId () == Task::Attack) { + if (!m_approachingLadderTimer.elapsed () || getCurrentTaskId () == Task::Attack) { return pev->v_angle; } return m_moveAngles; @@ -3769,10 +3772,13 @@ void Bot::runMovement () { // elapses, that bot will behave like a ghost : no movement, but bullets and players can // pass through it. Then, when the next frame will begin, the stucking problem will arise ! - m_frameInterval = game.time () - m_lastCommandTime; + m_frameInterval = game.time () - m_previousThinkTime; const auto msecVal = computeMsec (); - m_lastCommandTime = game.time (); + m_previousThinkTime = game.time (); + + // translate bot buttons + translateInput (); engfuncs.pfnRunPlayerMove (pev->pContainingEntity, getRpmAngles (), m_moveSpeed, m_strafeSpeed, @@ -3946,7 +3952,6 @@ void Bot::updateHearing () { // check if heard enemy can be shoot through some obstacle else { if (cv_shoots_thru_walls.bool_ () - && m_difficulty >= Difficulty::Normal && m_lastEnemy == hearedEnemy && rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct) && m_seeEnemyTime + 3.0f > game.time () diff --git a/src/combat.cpp b/src/combat.cpp index 4d923d8..0d232e8 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -7,7 +7,7 @@ #include -ConVar cv_shoots_thru_walls ("shoots_thru_walls", "1", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f); +ConVar cv_shoots_thru_walls ("shoots_thru_walls", "2", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f); ConVar cv_ignore_enemies ("ignore_enemies", "0", "Enables or disables searching world for enemies."); ConVar cv_check_enemy_rendering ("check_enemy_rendering", "0", "Enables or disables checking enemy rendering flags. Useful for some mods."); ConVar cv_check_enemy_invincibility ("check_enemy_invincibility", "0", "Enables or disables checking enemy invincibility. Useful for some mods."); @@ -236,11 +236,6 @@ bool Bot::seesEnemy (edict_t *player) { && frustum.check (m_viewFrustum, player) && !isBehindSmokeClouds (player->v.origin) && checkBodyParts (player)) { - - m_seeEnemyTime = game.time (); - m_lastEnemy = player; - m_lastEnemyOrigin = m_enemyOrigin; - return true; } return false; @@ -274,7 +269,13 @@ bool Bot::lookupEnemies () { } else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f > game.time () && util.isAlive (m_lastEnemy)) { m_states |= Sense::SuspectEnemy; - m_aimFlags |= AimFlags::LastEnemy; + + // check if last enemy can be penetrated + const auto penetratePower = conf.findWeaponById (m_currentWeapon).penetratePower * 4; + + if (isPenetrableObstacle3 (m_lastEnemyOrigin, penetratePower)) { + m_aimFlags |= AimFlags::LastEnemy; + } } m_enemyParts = Visibility::None; m_enemyOrigin = nullptr; @@ -475,7 +476,6 @@ bool Bot::lookupEnemies () { // if no enemy visible check if last one shoot able through wall if (cv_shoots_thru_walls.bool_ () - && m_difficulty >= Difficulty::Normal && rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct) && isPenetrableObstacle (newEnemy->v.origin)) { @@ -579,7 +579,7 @@ Vector Bot::getEnemyBodyOffset () { auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; // with to much recoil or using specific weapons choice to aim to the chest - if (distance > kSprayDistance && (isRecoilHigh () || usesSniperAWP () || usesShotgun ())) { + if (distance > kSprayDistance && (isRecoilHigh () || usesShotgun ())) { headshotPct = 0; } @@ -587,6 +587,10 @@ Vector Bot::getEnemyBodyOffset () { if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) { spot = m_enemyOrigin + getCustomHeight (distance); + if (usesSniper ()) { + spot.z -= pev->view_ofs.z * 0.5f; + } + // set's the enemy shooting spot to head, if headshot pct allows, and use head for that // enemy until new enemy is acquired, to prevent too shaky aiming m_enemyBodyPartSet = m_enemy; @@ -698,8 +702,6 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { // this function returns true if enemy can be shoot through some obstacle, false otherwise. // credits goes to Immortal_BLG - const auto method = cv_shoots_thru_walls.int_ (); - if (m_isUsingGrenade || m_difficulty < Difficulty::Normal) { return false; } @@ -708,6 +710,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { if (penetratePower == 0) { return false; } + const auto method = cv_shoots_thru_walls.int_ (); // switch methods switch (method) { @@ -989,7 +992,10 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } } } - m_shootTime = game.time (); + + if (pev->button & IN_ATTACK) { + m_shootTime = game.time (); + } } else { // don't attack with knife over long distance @@ -1239,8 +1245,7 @@ void Bot::attackMovement () { // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP if (!game.is (GameFlags::CSDM)) { - if ((m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) { - m_moveSpeed = -pev->maxspeed; + if (m_retreatTime < game.time () && (m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) { startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true); } else if (approach < 50) { diff --git a/src/control.cpp b/src/control.cpp index 97b0f9c..b473a40 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -10,6 +10,7 @@ ConVar cv_display_menu_text ("display_menu_text", "1", "Enables or disables display menu text, when players asks for menu. Useful only for Android.", true, 0.0f, 1.0f, Var::Xash3D); ConVar cv_password ("password", "", "The value (password) for the setinfo key, if user sets correct password, he's gains access to bot commands and menus.", false, 0.0f, 0.0f, Var::Password); ConVar cv_password_key ("password_key", "_ybpw", "The name of setinfo key used to store password to bot commands and menus.", false); +ConVar cv_bots_kill_on_endround ("bots_kill_on_endround", "0", "Allows to use classic bot kill on issuing end-round command in menus, instead of gamedll endround.", false); int BotControl::cmdAddBot () { enum args { alias = 1, difficulty, personality, team, model, name, max }; @@ -1025,7 +1026,7 @@ int BotControl::menuMain (int item) { break; case 4: - if (game.is (GameFlags::ReGameDLL)) { + if (game.is (GameFlags::ReGameDLL) && !cv_bots_kill_on_endround.bool_ ()) { game.serverCommand ("endround"); } else { diff --git a/src/manager.cpp b/src/manager.cpp index e6aad41..e125c43 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1151,7 +1151,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) { } m_basePing = rg.get (cv_ping_base_min.int_ (), cv_ping_base_max.int_ ()); - m_lastCommandTime = game.time () - 0.1f; + m_previousThinkTime = game.time () - 0.1f; m_frameInterval = game.time (); m_heavyTimestamp = game.time (); m_slowFrameTimestamp = 0.0f; @@ -1620,7 +1620,7 @@ void Bot::newRound () { if (rg.chance (50)) { pushChatterMessage (Chatter::NewRound); } - auto thinkFps = cr::clamp (cv_think_fps.float_ (), 24.0f, 90.0f); + auto thinkFps = cr::clamp (cv_think_fps.float_ (), 30.0f, 90.0f); auto updateInterval = 1.0f / thinkFps; if (game.is (GameFlags::Xash3D)) { @@ -1634,7 +1634,8 @@ void Bot::newRound () { else if (game.is (GameFlags::Legacy)) { updateInterval = 0.0f; // legacy games behaves strange, when this enabled } - m_updateInterval = updateInterval; + m_thinkDelay.interval = updateInterval; + m_commandDelay.interval = 1.0f / 60.0f; } void Bot::resetPathSearchType () { diff --git a/src/navigate.cpp b/src/navigate.cpp index beaefa8..def2fc1 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -820,9 +820,26 @@ void Bot::moveToGoal () { // press duck button if we need to if (m_pathFlags & NodeFlag::Crouch) { - constexpr auto kEndRouteThreshold = 3; + bool pressDuck = true; - if (!(m_pathFlags & (NodeFlag::Camp | NodeFlag::Goal)) || m_pathWalk.length () < kEndRouteThreshold) { + if (m_pathFlags & (NodeFlag::Camp | NodeFlag::Goal)) { + TraceResult tr {}; + + auto src = m_path->origin; + auto dst = m_path->origin; + + src.z += 12.0f; + dst.z += 18.0f + 28.0f; + + game.testLine (src, dst, TraceIgnore::Everything, ent (), &tr); + + if (tr.flFraction >= 0.95f) { + pressDuck = false; + } + } + + // press duck if not canceled by visibility count check only and it's end of the route + if (pressDuck) { pev->button |= IN_DUCK; } } @@ -899,7 +916,7 @@ bool Bot::updateNavigation () { m_navTimeset = game.time (); } - m_destOrigin = m_pathOrigin + pev->view_ofs; + m_destOrigin = m_pathOrigin; // this node has additional travel flags - care about them if (m_currentTravelFlags & PathFlag::Jump) { @@ -962,7 +979,7 @@ bool Bot::updateNavigation () { const float ladderDistance = pev->origin.distance (m_pathOrigin); if (m_pathOrigin.z >= pev->origin.z + 16.0f) { - constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f); + constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 36.0f); m_pathOrigin = m_path->origin + kLadderOffset; } @@ -979,13 +996,16 @@ bool Bot::updateNavigation () { const auto prevNodeIndex = m_previousNodes[0]; // do a precise movement when very near - if (!isDucking () && graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) { - m_moveSpeed = pev->maxspeed * 0.4f; + if (graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) { + if (!isDucking ()) { + m_moveSpeed = pev->maxspeed * 0.4f; + } // do not duck while not on ladder if (!isOnLadder ()) { pev->button &= ~IN_DUCK; } + m_approachingLadderTimer.start (m_frameInterval * 4.0f); } // special detection if someone is using the ladder (to prevent to have bots-towers on ladders) @@ -1133,7 +1153,7 @@ bool Bot::updateNavigation () { } } else if (m_pathFlags & NodeFlag::Ladder) { - desiredDistanceSq = cr::sqrf (8.0f); + desiredDistanceSq = cr::sqrf (16.0f); } else if (m_currentTravelFlags & PathFlag::Jump) { desiredDistanceSq = 0.0f; @@ -1142,7 +1162,7 @@ bool Bot::updateNavigation () { desiredDistanceSq = 0.0f; } else if (isOccupiedNode (m_path->number)) { - desiredDistanceSq = cr::sqrf (96.0f); + desiredDistanceSq = cr::sqrf (102.0f); } else { desiredDistanceSq = cr::max (cr::sqrf (m_path->radius), desiredDistanceSq); @@ -1157,9 +1177,9 @@ bool Bot::updateNavigation () { } // needs precise placement - check if we get past the point - if (desiredDistanceSq < cr::sqrf (22.0f) + if (desiredDistanceSq < cr::sqrf (16.0f) && nodeDistanceSq < cr::sqrf (30.0f) - && m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= nodeDistanceSq) { + && m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) > nodeDistanceSq) { desiredDistanceSq = nodeDistanceSq + 1.0f; } @@ -3127,7 +3147,7 @@ bool Bot::isReachableNode (int index) { const Vector &dst = graph[index].origin; // is the destination close enough? - if (dst.distanceSq (src) >= cr::sqrf (600.0f)) { + if (dst.distanceSq (src) >= cr::sqrf (400.0f)) { return false; } diff --git a/src/storage.cpp b/src/storage.cpp index 3776235..42ae17b 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -183,7 +183,15 @@ template bool BotStorage::load (SmallArray &data, ExtenHeader * // tell graph about exten header graph.setExtenHeader (&extenHeader); } - } + } + + // for visibility tables load counts of stand/count numbers + if (type.option & StorageOption::Vistable) { + for (auto &path : graph) { + file.read (&path.vis, sizeof (PathVis)); + } + } + ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); file.close (); @@ -248,7 +256,14 @@ template bool BotStorage::save (const SmallArray &data, ExtenHe hdr.uncompressed = static_cast (rawLength); file.write (&hdr, sizeof (StorageHeader)); - file.write (compressed.data (), sizeof (uint8_t), compressedLength); + file.write (compressed.data (), sizeof (uint8_t), compressedLength); + + // for visibility tables save counts of stand/count numbers + if (type.option & StorageOption::Vistable) { + for (auto &path : graph) { + file.write (&path.vis, sizeof (PathVis)); + } + } // add extension if ((type.option & StorageOption::Exten) && exten != nullptr) { diff --git a/src/tasks.cpp b/src/tasks.cpp index 454a742..04ec636 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -33,7 +33,7 @@ void Bot::normal_ () { if (m_currentNodeIndex == debugGoal) { const auto &debugOrigin = graph[debugGoal].origin; - if (debugOrigin.distanceSq (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) { + if (debugOrigin.distanceSq2d (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) { m_moveToGoal = false; m_checkTerrain = false; @@ -468,10 +468,10 @@ void Bot::seekCover_ () { destIndex = getTask ()->data; } else { - destIndex = findCoverNode (usesSniper () ? 256.0f : 512.0f); + destIndex = findCoverNode (900.0f); if (destIndex == kInvalidNodeIndex) { - m_retreatTime = game.time () + rg.get (5.0f, 10.0f); + m_retreatTime = game.time () + rg.get (1.0f, 2.0f); m_prevGoalIndex = kInvalidNodeIndex; completeTask (); diff --git a/src/vision.cpp b/src/vision.cpp index f4b251c..a37edc6 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -175,33 +175,36 @@ void Bot::setAimDirection () { m_lookAt = m_lookAtSafe; } else if (flags & AimFlags::Nav) { - m_lookAt = m_destOrigin; + const auto &destOrigin = m_destOrigin + pev->view_ofs; + m_lookAt = destOrigin; + if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () - && !m_isStuck && m_moveSpeed > getShiftSpeed () - && !(pev->button & IN_DUCK) + && !m_isStuck && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch)) - && m_pathWalk.hasNext () - && pev->origin.distanceSq (m_destOrigin) < cr::sqrf (512.0f)) { + && m_pathWalk.hasNext () && !isOnLadder () + && pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) { const auto nextPathIndex = m_pathWalk.next (); const auto doubleNextPath = m_pathWalk.doubleNext (); if (vistab.visible (m_currentNodeIndex, doubleNextPath)) { - m_lookAt = graph[doubleNextPath].origin + pev->view_ofs; + const auto &gn = graph[doubleNextPath]; + m_lookAt = gn.origin + pev->view_ofs; } else if (vistab.visible (m_currentNodeIndex, nextPathIndex)) { - m_lookAt = graph[nextPathIndex].origin + pev->view_ofs; + const auto &gn = graph[nextPathIndex]; + m_lookAt = gn.origin + pev->view_ofs; } else { - m_lookAt = m_destOrigin; + m_lookAt = pev->origin + pev->view_ofs + pev->v_angle.forward () * 300.0f; } } else { - m_lookAt = m_destOrigin; + m_lookAt = destOrigin; } - const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || !cr::fzero (pev->velocity.z); + const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder (); if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !horizontalMovement) { const auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex); @@ -211,7 +214,7 @@ void Bot::setAimDirection () { && !(graph[dangerIndex].flags & NodeFlag::Crouch)) { if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (512.0f)) { - m_lookAt = m_destOrigin; + m_lookAt = destOrigin; } else { m_lookAt = graph[dangerIndex].origin + pev->view_ofs; @@ -225,23 +228,20 @@ void Bot::setAimDirection () { // try look at next node if on ladder if (horizontalMovement && m_pathWalk.hasNext ()) { const auto &nextPath = graph[m_pathWalk.next ()]; - + if ((nextPath.flags & NodeFlag::Ladder) && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (128.0f) && nextPath.origin.z > m_pathOrigin.z + 26.0f) { - m_lookAt = nextPath.origin; - } - else { - m_lookAt = m_destOrigin; + m_lookAt = nextPath.origin + pev->view_ofs; } } + // don't look at bottom of node, if reached it + if (m_lookAt == destOrigin && !horizontalMovement) { + m_lookAt.z = getEyesPos ().z; + } + // try to look at last victim for a little, maybe there's some one else if (game.isNullEntity (m_enemy) && m_difficulty >= Difficulty::Normal && !m_forgetLastVictimTimer.elapsed () && !m_lastVictimOrigin.empty ()) { - m_lookAt = m_lastVictimOrigin; - } - - // don't look at bottom of node, if reached it - if (m_lookAt == m_destOrigin && !horizontalMovement) { - m_lookAt.z = getEyesPos ().z; + m_lookAt = m_lastVictimOrigin + pev->view_ofs; } } @@ -402,21 +402,15 @@ void Bot::updateLookAngles () { updateBodyAngles (); return; } - float aimSkill = cr::clamp (static_cast (m_difficulty), 3.0f, 4.0f) * 25.0f; + const bool importantAimFlags = (m_aimFlags & (AimFlags::Enemy | AimFlags::Grenade)); - // do not slowdown while on ladder - if (isOnLadderPath () || isOnLadder ()) { - aimSkill = 100.0f; - } - const bool importantAimFlags = (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)); - - float accelerate = aimSkill * 30.0f; - float stiffness = aimSkill * 2.0f; - float damping = aimSkill * 0.25f; + float accelerate = 3000.0f; + float stiffness = 200.0f; + float damping = 25.0f; if ((importantAimFlags || m_wantsToFire) && m_difficulty > Difficulty::Normal) { if (m_difficulty == Difficulty::Expert) { - accelerate += 600.0f; + accelerate += 300.0f; } stiffness += 100.0f; damping -= 5.0f; @@ -427,8 +421,8 @@ void Bot::updateLookAngles () { float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); // prevent reverse facing angles when navigating normally - if (m_difficulty < Difficulty::Easy && m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) { - const float forward = (m_pathOrigin - pev->origin).yaw (); + if (m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) { + const float forward = (m_lookAt - pev->origin).yaw (); if (!cr::fzero (forward)) { const float current = cr::wrapAngle (pev->v_angle.y - forward); @@ -460,9 +454,11 @@ void Bot::updateLookAngles () { const float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate); m_lookPitchVel += delta * accel; - m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f); + m_idealAngles.x += delta * m_lookPitchVel; - pev->v_angle = m_idealAngles.clampAngles (); + m_idealAngles.x = cr::clamp (m_idealAngles.x, -89.0f, 89.0f); + + pev->v_angle = m_idealAngles; pev->v_angle.z = 0.0f; updateBodyAngles ();