From 286e1c8621fff0aa499e88744c953a9e6899631f Mon Sep 17 00:00:00 2001 From: commandcobra7 <91374215+commandcobra7@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:44:07 +0300 Subject: [PATCH] bot: perform a seek cover search mission to avoid infected creatures. (#707) bot: perform a seek cover search mission to avoid infected creatures. nav: several changes in controlling the terrain. --- cfg/addons/yapb/conf/yapb.cfg | 11 ++++- inc/graph.h | 2 +- inc/yapb.h | 9 ++-- src/botlib.cpp | 90 +++++++++++++++++++++++------------ src/combat.cpp | 40 ++++++++-------- src/graph.cpp | 2 +- src/manager.cpp | 2 +- src/navigate.cpp | 79 +++++++++++++++++------------- src/tasks.cpp | 32 +++++-------- src/vision.cpp | 2 +- 10 files changed, 155 insertions(+), 114 deletions(-) diff --git a/cfg/addons/yapb/conf/yapb.cfg b/cfg/addons/yapb/conf/yapb.cfg index e4811d8..be7fae7 100644 --- a/cfg/addons/yapb/conf/yapb.cfg +++ b/cfg/addons/yapb/conf/yapb.cfg @@ -305,6 +305,13 @@ yb_use_engine_pvs_check "0" // yb_use_hitbox_enemy_targeting "0" +// +// Bots will consider glass when deciding to shoot enemies. Required for very special maps only. +// --- +// Default: "0", Min: "0", Max: "1" +// +yb_aim_trace_consider_glass "0" + // // Binds specified key for opening bots menu. // --- @@ -777,9 +784,9 @@ yb_random_knife_attacks "1" // // Maximum number for path length, to predict the enemy. // --- -// Default: "25", Min: "15", Max: "256" +// Default: "22", Min: "15", Max: "256" // -yb_max_nodes_for_predict "25" +yb_max_nodes_for_predict "22" // // Enables or disables extra hard difficulty for bots. diff --git a/inc/graph.h b/inc/graph.h index d5ff895..11f9e5e 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -265,7 +265,7 @@ public: void syncCollectOnline (); void collectOnline (); - IntArray getNearestInRadius (float radius, const Vector &origin, int maxCount = -1); + IntArray getNearestInRadius (const float radius, const Vector &origin, int maxCount = -1); public: StringRef getAuthor () const { diff --git a/inc/yapb.h b/inc/yapb.h index 396dae5..178e6d4 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -82,7 +82,7 @@ public: int penetratePower, int maxClip, int type, - bool fireHold) : id (id), name (name), model (model), price (price), minPrimaryAmmo (minPriAmmo), teamStandard (teamStd), + bool fireHold) : id (id), name (name), model (model), price (price), minPrimaryAmmo (minPriAmmo), teamStandard (teamStd), teamAS (teamAs), buyGroup (buyGroup), buySelect (buySelect), buySelectT (buySelectT), buySelectCT (buySelectCT), penetratePower (penetratePower), maxClip (maxClip), type (type), primaryFireHold (fireHold) { } @@ -244,6 +244,7 @@ private: float m_prevTime {}; // time previously checked movement speed float m_heavyTimestamp {}; // is it time to execute heavy-weight functions 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 @@ -297,6 +298,7 @@ private: float m_lastVictimTime {}; // time when bot killed an enemy float m_killsInterval {}; // interval between kills float m_lastDamageTimestamp {}; // last damage from take damage fn + float m_movedDistance {}; // bot moved distance bool m_moveToGoal {}; // bot currently moving to goal?? bool m_isStuck {}; // bot is stuck @@ -318,6 +320,7 @@ private: bool m_needToSendWelcomeChat {}; // bot needs to greet people on server? bool m_isCreature {}; // bot is not a player, but something else ? zombie ? bool m_isOnInfectedTeam {}; // bot is zombie (this assumes bot is a creature) + bool m_infectedEnemyTeam {}; // the enemy is a zombie (assumed to be a hostile creature) 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 @@ -360,7 +363,6 @@ private: Vector m_breakableOrigin {}; // origin of breakable Vector m_rightRef {}; // right referential vector Vector m_checkFallPoint[2] {}; // check fall point - Vector m_prevVelocity {}; // velocity some frames before Array m_ignoredBreakable {}; // list of ignored breakables Array m_ignoredItems {}; // list of pointers to entity to ignore for pickup @@ -375,7 +377,6 @@ private: CountdownTimer m_forgetLastVictimTimer {}; // time to forget last victim position ? CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder CountdownTimer m_lostReachableNodeTimer {}; // bot's issuing next node, probably he's lost - CountdownTimer m_fixFallTimer {}; // timer we're fixed fall last time CountdownTimer m_repathTimer {}; // bots is going to repath his route private: @@ -493,7 +494,7 @@ private: void postProcessGoals (const IntArray &goals, int result[]); void updatePickups (); void ensurePickupEntitiesClear (); - void checkTerrain (float movedDistance, const Vector &dirNormal); + void checkTerrain (const Vector &dirNormal); void checkFall (); void checkDarkness (); void checkParachute (); diff --git a/src/botlib.cpp b/src/botlib.cpp index 573a7ed..efbe56a 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -157,7 +157,10 @@ void Bot::checkBreakable (edict_t *touch) { } if (game.isNullEntity (touch)) { - m_breakableEntity = lookupBreakable (); + auto breakable = lookupBreakable (); + + m_breakableEntity = breakable; + m_breakableOrigin = game.getEntityOrigin (breakable); } else { if (m_breakableEntity != touch) { @@ -225,7 +228,7 @@ void Bot::checkBreakablesAround () { // maybe time to give up? if (m_lastBreakable == breakable && m_breakableTime + 1.5f < game.time ()) { - m_ignoredBreakable.emplace (breakable); + m_ignoredBreakable.push (breakable); m_breakableOrigin.clear (); m_lastBreakable = nullptr; @@ -305,7 +308,7 @@ edict_t *Bot::lookupBreakable () { return hit; } m_breakableEntity = nullptr; - m_breakableOrigin = nullptr; + m_breakableOrigin.clear (); return nullptr; } @@ -1702,7 +1705,7 @@ void Bot::overrideConditions () { // then start escape from bomb immediate startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true); } - float reachEnemyWikKnifeDistanceSq = cr::sqrf (128.0f); + float reachEnemyKnifeDistanceSq = cr::sqrf (128.0f); // special handling, if we have a knife in our hands if (isKnifeMode () && (util.isPlayer (m_enemy) || (cv_attack_monsters && util.isMonster (m_enemy)))) { @@ -1710,12 +1713,12 @@ void Bot::overrideConditions () { const auto nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin); if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex) { - reachEnemyWikKnifeDistanceSq = graph[nearestToEnemyPoint].origin.distanceSq (m_enemy->v.origin); - reachEnemyWikKnifeDistanceSq += cr::sqrf (48.0f); + reachEnemyKnifeDistanceSq = graph[nearestToEnemyPoint].origin.distanceSq (m_enemy->v.origin); + reachEnemyKnifeDistanceSq += cr::sqrf (48.0f); } // do nodes movement if enemy is not reachable with a knife - if (distanceSq2d > reachEnemyWikKnifeDistanceSq && (m_states & Sense::SeeingEnemy)) { + if (distanceSq2d > reachEnemyKnifeDistanceSq && (m_states & Sense::SeeingEnemy)) { if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex && cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) { @@ -1725,22 +1728,18 @@ void Bot::overrideConditions () { if (tid != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) { startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true); } - else { - if (tid == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) { - clearTask (Task::MoveToPosition); - startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true); - } + else if (tid == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) { + clearTask (Task::MoveToPosition); + startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true); } } } - else { - if (!m_isCreature - && distanceSq2d <= reachEnemyWikKnifeDistanceSq - && (m_states & Sense::SeeingEnemy) - && tid == Task::MoveToPosition) { + else if (!m_isCreature + && distanceSq2d <= reachEnemyKnifeDistanceSq + && (m_states & Sense::SeeingEnemy) + && tid == Task::MoveToPosition) { - clearTask (Task::MoveToPosition); // remove any move tasks - } + clearTask (Task::MoveToPosition); // remove any move tasks } } @@ -1781,6 +1780,18 @@ void Bot::overrideConditions () { m_navTimeset = game.time (); } } + + // start searching for seek cover to avoid infected creatures + if (game.is (GameFlags::ZombieMod) + && !m_isCreature + && m_infectedEnemyTeam + && util.isAlive (m_enemy) + && m_retreatTime < game.time () + && pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (512.0f)) { + + completeTask (); + startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true); + } } void Bot::syncUpdatePredictedIndex () { @@ -2332,8 +2343,21 @@ bool Bot::isEnemyThreat () { return false; } + auto isOnAttackDistance = [&] (edict_t *enemy, float distance) -> bool { + if (enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (distance)) { + return true; + } + + if (usesKnife ()) { + if (enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (distance)) { + return true; + } + } + return false; + }; + // if enemy is near or facing us directly - if (m_enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (256.0f) || (!usesKnife () && isInViewCone (m_enemy->v.origin))) { + if (isOnAttackDistance (m_enemy, 256.0f) || (!usesKnife () && isInViewCone (m_enemy->v.origin))) { return true; } return false; @@ -2350,7 +2374,7 @@ bool Bot::reactOnEnemy () { if (m_isCreature && !game.isNullEntity (m_enemy)) { m_isEnemyReachable = false; - if (pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (118.0f)) { + if (pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (128.0f)) { m_navTimeset = game.time (); m_isEnemyReachable = true; } @@ -3043,7 +3067,7 @@ void Bot::update () { m_canChooseAimDirection = true; m_isAlive = util.isAlive (ent ()); m_team = game.getTeam (ent ()); - m_healthValue = cr::clamp (pev->health, 0.0f, 100.0f); + m_healthValue = cr::clamp (pev->health, 0.0f, 99999.9f); if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { m_hasC4 = !!(pev->weapons & cr::bit (Weapon::C4)); @@ -3060,6 +3084,11 @@ void Bot::update () { } m_isCreature = isCreature (); + // damage victim action + if (m_lastDamageTimestamp < game.time () && !cr::fzero (m_lastDamageTimestamp)) { + m_lastDamageTimestamp = 0.0f; + } + // is bot movement enabled m_botMovement = false; @@ -3262,7 +3291,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 = kMinMovedDistance; // length of different vector (distance bot moved) + m_movedDistance = kMinMovedDistance; // length of different vector (distance bot moved) resetMovement (); @@ -3289,7 +3318,7 @@ void Bot::logic () { // see how far bot has moved since the previous position... if (m_checkTerrain) { - movedDistance = m_prevOrigin.distance (pev->origin); + m_movedDistance = m_prevOrigin.distance (pev->origin); } // save current position as previous @@ -3397,7 +3426,7 @@ void Bot::logic () { checkBreakable (nullptr); doPlayerAvoidance (dirNormal); - checkTerrain (movedDistance, dirNormal); + checkTerrain (dirNormal); } // if we have fallen from the place of move, the nearest point is allowed @@ -3443,7 +3472,7 @@ void Bot::logic () { // save the previous speed (for checking if stuck) m_prevSpeed = cr::abs (m_moveSpeed); - m_prevVelocity = pev->velocity; + m_prevVelocity = cr::abs (pev->velocity.length2d ()); m_lastDamageType = -1; // reset damage } @@ -3953,7 +3982,7 @@ Vector Bot::isBombAudible () { } // we hear bomb if length greater than radius - if (cr::sqrf (desiredRadius) < pev->origin.distanceSq2d (bombOrigin)) { + if (pev->origin.distanceSq2d (bombOrigin) > cr::sqrf (desiredRadius)) { return bombOrigin; } return nullptr; @@ -4306,9 +4335,7 @@ float Bot::getShiftSpeed () { if (getCurrentTaskId () == Task::SeekCover || (m_aimFlags & AimFlags::Enemy) || isDucking () - || m_isCreature - || (pev->button & IN_DUCK) - || (m_oldButtons & IN_DUCK) + || ((pev->button | pev->oldbuttons) & IN_DUCK) || (m_currentTravelFlags & PathFlag::Jump) || (m_pathFlags & NodeFlag::Ladder) || isOnLadder () @@ -4337,9 +4364,10 @@ void Bot::refreshCreatureStatus (char *infobuffer) { // if bot is on infected team, and zombie mode is active, assume bot is a creature/zombie m_isOnInfectedTeam = game.getRealTeam (ent ()) == infectedTeam; + m_infectedEnemyTeam = game.getRealTeam (m_enemy) == infectedTeam; // do not process next if already infected - if (m_isOnInfectedTeam) { + if (m_isOnInfectedTeam || m_infectedEnemyTeam) { return; } } diff --git a/src/combat.cpp b/src/combat.cpp index 46e3e0f..91ecc21 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -669,8 +669,7 @@ Vector Bot::getBodyOffsetError (float distance) { m_aimLastError = Vector ( rg (mins.x * hitError, maxs.x * hitError), rg (mins.y * hitError, maxs.y * hitError), - rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f) - ); + rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f)); const auto &aimError = conf.getDifficultyTweaks (m_difficulty)->aimError; m_aimLastError += Vector (rg (-aimError.x, aimError.x), rg (-aimError.y, aimError.y), rg (-aimError.z, aimError.z)); @@ -779,15 +778,15 @@ Vector Bot::getCustomHeight (float distance) const { }; constexpr float kOffsetRanges[9][3] = { - { 0.0f, 0.0f, 0.0f }, // none - { 0.0f, 0.0f, 0.0f }, // melee - { 0.5f, -0.1f, -1.5f }, // pistol - { 6.5f, 6.0f, -2.0f }, // shotgun - { 0.5f, -7.5f, -9.5f }, // zoomrifle - { 0.5f, -7.5f, -9.5f }, // rifle - { 0.5f, -7.5f, -9.5f }, // smg - { 0.0f, -2.5f, -6.0f }, // sniper - { 1.5f, -4.0f, -9.0f } // heavy + { 0.0f, 0.0f, 0.0f }, // none + { 0.0f, 0.0f, 0.0f }, // melee + { 0.5f, -0.1f, -1.5f }, // pistol + { 6.5f, 6.0f, -2.0f }, // shotgun + { 0.5f, -7.5f, -9.5f }, // zoomrifle + { 0.5f, -7.5f, -9.5f }, // rifle + { 0.5f, -7.5f, -9.5f }, // smg + { 0.0f, -2.5f, -6.0f }, // sniper + { 1.5f, -4.0f, -9.0f } // heavy }; // only high-skilled bots do that @@ -815,7 +814,7 @@ bool Bot::isFriendInLineOfFire (float distance) const { } TraceResult tr {}; - game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle.normalize (), TraceIgnore::None, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + pev->v_angle.normalize_apx () * distance, TraceIgnore::None, ent (), &tr); // check if we hit something if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) { @@ -1125,7 +1124,7 @@ void Bot::selectWeapons (float distance, int, int id, int choosen) { else if (isShieldDrawn () || m_isReloading || (hasEnemy && (m_enemy->v.button & IN_RELOAD)) - || (hasEnemy && !seesEntity (m_enemy->v.origin))) { + || (!hasEnemy && !seesEntity (m_enemy->v.origin))) { pev->button |= IN_ATTACK2; // draw out the shield } @@ -1608,9 +1607,9 @@ void Bot::attackMovement () { if (m_difficulty >= Difficulty::Normal && distanceSq < cr::sqrf (kSprayDistance) && (m_jumpTime + 5.0f < game.time () - && isOnFloor () - && rg (0, 1000) < (m_isReloading ? 8 : 2) - && pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) { + && isOnFloor () + && rg (0, 1000) < (m_isReloading ? 8 : 2) + && pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) { pev->button |= IN_JUMP; } @@ -1814,14 +1813,13 @@ bool Bot::hasAnyAmmoInClip () { } bool Bot::isKnifeMode () { - return cv_jasonmode || - (usesKnife () && !hasAnyWeapons ()) + return cv_jasonmode || (usesKnife () && !hasAnyWeapons ()) || m_isCreature || ((m_states & Sense::SeeingEnemy) && usesKnife () && !hasAnyAmmoInClip ()); } bool Bot::isGrenadeWar () { - const bool hasSomeGreandes = bestGrenadeCarried () != -1; + const bool hasSomeGreandes = bestGrenadeCarried () != kGrenadeInventoryEmpty; // if has grenade an not other weapons, assume we're in grenade war if (!hasAnyWeapons () && hasSomeGreandes) { @@ -2113,10 +2111,10 @@ void Bot::checkReload () { } float Bot::calculateScaleFactor (edict_t *ent) const { - Vector entSize = ent->v.maxs - ent->v.mins; + const auto &entSize = ent->v.maxs - ent->v.mins; const float entArea = 2.0f * (entSize.x * entSize.y + entSize.y * entSize.z + entSize.x * entSize.z); - Vector botSize = pev->maxs - pev->mins; + const auto &botSize = pev->maxs - pev->mins; const float botArea = 2.0f * (botSize.x * botSize.y + botSize.y * botSize.z + botSize.x * botSize.z); return entArea / botArea; diff --git a/src/graph.cpp b/src/graph.cpp index 9b5aed2..06c8d2a 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -566,7 +566,7 @@ int BotGraph::getNearest (const Vector &origin, const float range, int flags) { return index; } -IntArray BotGraph::getNearestInRadius (float radius, const Vector &origin, int maxCount) { +IntArray BotGraph::getNearestInRadius (const float radius, const Vector &origin, int maxCount) { // returns all nodes within radius from position const float radiusSq = cr::sqrf (radius); diff --git a/src/manager.cpp b/src/manager.cpp index 2dfa320..335570f 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1518,6 +1518,7 @@ void Bot::newRound () { m_askCheckTime = rg (30.0f, 90.0f); m_minSpeed = 260.0f; m_prevSpeed = 0.0f; + m_prevVelocity = 0.0f; m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance); m_prevTime = game.time (); m_lookUpdateTime = game.time (); @@ -1602,7 +1603,6 @@ void Bot::newRound () { m_approachingLadderTimer.invalidate (); m_forgetLastVictimTimer.invalidate (); m_lostReachableNodeTimer.invalidate (); - m_fixFallTimer.invalidate (); m_repathTimer.invalidate (); for (auto &timer : m_chatterTimes) { diff --git a/src/navigate.cpp b/src/navigate.cpp index bcae486..cc459aa 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -173,7 +173,10 @@ int Bot::findBestGoal () { float campDesire = rg (0.0f, 100.0f) + defensive; if (!usesCampGun ()) { - campDesire *= 0.5f; + campDesire = 0.0f; + } + else if (usesSniper ()) { + campDesire = rg (1.5f, 2.5f) * campDesire; } int tactic = GoalTactic::Defensive; @@ -285,14 +288,14 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { else if (tactic == GoalTactic::Goal && !graph.m_goalPoints.empty ()) { // map goal node // force bomber to select closest goal, if round-start goal was reset by something - if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ()) { + if (m_isVIP || (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ())) { float nearestDistanceSq = kInfiniteDistance; int count = 0; for (const auto &point : graph.m_goalPoints) { const float distanceSq = graph[point].origin.distanceSq (pev->origin); - if (distanceSq > cr::sqrf (1024.0f)) { + if (distanceSq > cr::sqrf (1024.0f) || isGroupOfEnemies (graph[point].origin)) { continue; } if (distanceSq < nearestDistanceSq) { @@ -589,7 +592,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { } } -void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { +void Bot::checkTerrain (const Vector &dirNormal) { // if avoiding someone do not consider stuck TraceResult tr {}; @@ -599,6 +602,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // minimal speed for consider stuck const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4; + const auto randomProbeTime = rg (0.75f, 1.15f); // standing still, no need to check? if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed) @@ -612,7 +616,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { m_firstCollideTime = 0.0f; } // didn't we move enough previously? - else if (movedDistance < kMinMovedDistance && m_prevSpeed > 20.0f) { + else if (m_movedDistance < kMinMovedDistance && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) { m_prevTime = game.time (); // then consider being stuck m_isStuck = true; @@ -642,9 +646,16 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } } + if (m_probeTime >= game.time ()) { + m_isStuck = true; + } + else if (m_probeTime + randomProbeTime < game.time () && !cr::fzero (m_probeTime)) { + resetCollision (); // resets its collision state because it was too long time in probing state + } + // not stuck? if (!m_isStuck) { - if (m_probeTime + rg (0.75f, 1.15f) < game.time ()) { + if (m_probeTime + randomProbeTime < game.time ()) { resetCollision (); // reset collision memory if not being stuck for 0.5 secs } else { @@ -845,7 +856,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } m_collideTime = game.time (); - m_probeTime = game.time () + 0.5f; + m_probeTime = game.time () + randomProbeTime; m_collisionProbeBits = bits; m_collisionState = CollisionState::Probing; m_collStateIndex = 0; @@ -855,10 +866,9 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { if (m_collisionState == CollisionState::Probing) { if (m_probeTime < game.time ()) { m_collStateIndex++; - m_probeTime = game.time () + 0.5f; + m_probeTime = game.time () + randomProbeTime; if (m_collStateIndex >= kMaxCollideMoves) { - m_navTimeset = game.time () - 5.0f; resetCollision (); } } @@ -867,7 +877,13 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { switch (m_collideMoves[m_collStateIndex]) { case CollisionState::Jump: if ((isOnFloor () || isInWater ()) && !isOnLadder ()) { - pev->button |= IN_JUMP; + if (isInWater () + || !m_isCreature + || m_lastDamageTimestamp < game.time () + || (m_currentTravelFlags & PathFlag::Jump)) { + + pev->button |= IN_JUMP; + } } break; @@ -894,10 +910,8 @@ void Bot::checkFall () { if (isPreviousLadder ()) { return; } - else if (graph.exists (m_currentNodeIndex)) { - if (graph[m_currentNodeIndex].flags & NodeFlag::Ladder) { - return; - } + else if ((m_pathFlags & NodeFlag::Ladder) && isPreviousLadder () && isOnLadder ()) { + return; } if (!m_checkFall) { @@ -905,7 +919,7 @@ void Bot::checkFall () { m_checkFallPoint[0] = pev->origin; if (!game.isNullEntity (m_enemy)) { - m_checkFallPoint[1] = game.getEntityOrigin (m_enemy); + m_checkFallPoint[1] = m_enemy->v.origin; } else if (m_currentNodeIndex != kInvalidNodeIndex) { m_checkFallPoint[1] = m_pathOrigin; @@ -921,7 +935,7 @@ void Bot::checkFall () { } } - if (!m_checkFall || !isOnFloor () || !m_fixFallTimer.elapsed ()) { + if (!m_checkFall || !isOnFloor ()) { return; } m_checkFall = false; @@ -931,25 +945,23 @@ void Bot::checkFall () { const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]); if (nowDistanceSq > baseDistanceSq - && (nowDistanceSq > baseDistanceSq * 1.8f || nowDistanceSq > baseDistanceSq + 260.0f) - && baseDistanceSq >= cr::sqrf (124.0f) && nowDistanceSq >= cr::sqrf (146.0f)) { + && (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f) + && baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) { fixFall = true; } - else if (cr::abs (m_checkFallPoint[1].z) > cr::abs (pev->origin.z) + 138.0f - || cr::abs (m_checkFallPoint[0].z) > cr::abs (pev->origin.z) + 138.0f) { + 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) - && cr::abs (m_checkFallPoint[1].z) > cr::abs (pev->origin.z) + 72.0f) { + && nowDistanceSq > cr::sqrf (16.0f) + && m_checkFallPoint[1].z > pev->origin.z + 62.0f) { fixFall = true; } if (fixFall) { m_currentNodeIndex = kInvalidNodeIndex; findValidNode (); - - m_fixFallTimer.start (1.0f); } } @@ -1295,8 +1307,8 @@ bool Bot::updateNavigation () { 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 ()) { + if (distanceSq < cr::sqrf (96.0f)) { + if ((dirToPoint | forwardMove) < 0.0f) { m_moveSpeed = -pev->maxspeed; } } @@ -1308,7 +1320,7 @@ bool Bot::updateNavigation () { } float desiredDistanceSq = cr::sqrf (8.0f); - float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin); + const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin); // initialize the radius for a special node type, where the node is considered to be reached if (m_pathFlags & NodeFlag::Lift) { @@ -1948,8 +1960,7 @@ bool Bot::findNextBestNodeEx (const IntArray &data, bool handleFails) { float Bot::getEstimatedNodeReachTime () { const bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) || (m_pathFlags & NodeFlag::Ladder) - || (pev->button & IN_DUCK) - || (m_oldButtons & IN_DUCK); + || ((pev->button | pev->oldbuttons) & IN_DUCK); float estimatedTime = longTermReachability ? 8.5f : 3.5f; @@ -1958,6 +1969,10 @@ float Bot::getEstimatedNodeReachTime () { return estimatedTime; } + if (m_lastDamageTimestamp < game.time () && !cr::fzero (m_lastDamageTimestamp) && !m_isStuck && m_isCreature) { + return estimatedTime; + } + // calculate 'real' time that we need to get from one node to another if (graph.exists (m_currentNodeIndex) && graph.exists (m_previousNodes[0])) { const float distanceSq = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin); @@ -3097,7 +3112,7 @@ bool Bot::isBlockedLeft () { pev->angles.angleVectors (&forward, &right, nullptr); // do a trace to the left... - game.testLine (pev->origin, forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr); + game.testLine (pev->origin, pev->origin - forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !util.isDoorEntity (tr.pHit)) { @@ -3110,8 +3125,8 @@ bool Bot::isBlockedRight () { TraceResult tr {}; float direction = 48.0f; - if (m_moveSpeed < 0.0f) { - direction = -48.0f; + if (m_moveSpeed > 0.0f) { + direction = 48.0f; } Vector right {}, forward {}; pev->angles.angleVectors (&forward, &right, nullptr); diff --git a/src/tasks.cpp b/src/tasks.cpp index 81e6f74..bb0662f 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -121,7 +121,7 @@ void Bot::normal_ () { if (allowedCampWeapon && m_timeCamping + 10.0f < game.time () && !m_hasHostage) { bool campingAllowed = true; - // Check if it's not allowed for this team to camp here + // check if it's not allowed for this team to camp here if (m_team == Team::Terrorist) { if (m_pathFlags & NodeFlag::CTOnly) { campingAllowed = false; @@ -134,27 +134,20 @@ void Bot::normal_ () { } // don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp - if (campingAllowed - && (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4))) { + if (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4)) { campingAllowed = false; } // check if another bot is already camping here - if (campingAllowed && isOccupiedNode (m_currentNodeIndex)) { + if (isOccupiedNode (m_currentNodeIndex)) { campingAllowed = false; } // skip sniper node if we don't have sniper weapon - if (campingAllowed && !usesSniper () && (m_pathFlags & NodeFlag::Sniper)) { + if (!usesSniper () && (m_pathFlags & NodeFlag::Sniper)) { 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) { @@ -406,13 +399,12 @@ void Bot::huntEnemy_ () { } // bots skill higher than 60? - if (cv_walking_allowed && mp_footsteps && m_difficulty >= Difficulty::Normal && !isKnifeMode ()) { + if (cv_walking_allowed && mp_footsteps && m_difficulty >= Difficulty::Normal) { + // then make him move slow if near enemy - if (!(m_currentTravelFlags & PathFlag::Jump)) { - if (m_currentNodeIndex != kInvalidNodeIndex) { - if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time ()) { - m_moveSpeed = getShiftSpeed (); - } + if (m_currentNodeIndex != kInvalidNodeIndex && !(m_currentTravelFlags & PathFlag::Jump)) { + if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time ()) { + m_moveSpeed = getShiftSpeed (); } } } @@ -623,7 +615,7 @@ void Bot::blind_ () { } void Bot::camp_ () { - if (!cv_camping_allowed || m_isCreature) { + if (!cv_camping_allowed || isKnifeMode ()) { completeTask (); return; } @@ -1480,7 +1472,7 @@ void Bot::shootBreakable_ () { m_ignoredBreakable.push (tr.pHit); m_breakableEntity = nullptr; - m_breakableOrigin = nullptr; + m_breakableOrigin.clear (); completeTask (); return; @@ -1514,7 +1506,7 @@ void Bot::shootBreakable_ () { // if with knife with no ammo, recompute breakable distance if (!hasAnyAmmoInClip () && usesKnife () - && distToObstacle > cr::sqrf (72.0f)) { + && distToObstacle > cr::sqrf (32.0f)) { completeTask (); } diff --git a/src/vision.cpp b/src/vision.cpp index 252d89e..30c14fa 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -433,7 +433,7 @@ void Bot::setAimDirection () { } // don't switch view right away after loosing focus with current enemy - if ((m_shootTime + 1.5f > game.time () || m_seeEnemyTime + 1.5f > game.time ()) + if (m_seeEnemyTime + 1.5f > game.time () && m_forgetLastVictimTimer.elapsed () && !m_lastEnemyOrigin.empty () && util.isAlive (m_lastEnemy)