diff --git a/cfg/addons/yapb/conf/difficulty.cfg b/cfg/addons/yapb/conf/difficulty.cfg index df59ea6..cac7c2d 100644 --- a/cfg/addons/yapb/conf/difficulty.cfg +++ b/cfg/addons/yapb/conf/difficulty.cfg @@ -19,11 +19,12 @@ ; headshotProbability - Probability bot should aim at head instead of body if body and head both visible. ; seenThruWallChance - Chance the bot will attack enemy if he believes he's there and just seen him. ; heardThruWallChance - Chance the bot will attack enemy if he believes he's there and just heard him. +; maxWeaponRecoil - Maximum weapon recoil to compensate by pausing fire. +; aimError - (x, y, z) offsets to add aim error to bot aiming ; -Noob = 0.8, 1.0, 5, 0, 0 -Easy = 0.6, 0.8, 30, 10, 10 -Normal = 0.4, 0.6, 50, 30, 40 -Hard = 0.2, 0.4, 75, 60, 70 -Expert = 0.1, 0.2, 100, 90, 90 - +Noob = 0.8, 1.0, 5, 0, 0, 38, 30.0, 30.0, 40.0 +Easy = 0.6, 0.8, 10, 0, 10, 32, 15.0, 15.0, 24.0 +Normal = 0.4, 0.6, 50, 30, 40, 26, 5.0, 5.0, 10.0 +Hard = 0.2, 0.4, 75, 60, 70, 23, 0.0, 0.0, 0.0 +Expert = 0.1, 0.2, 100, 90, 90, 21, 0.0, 0.0, 0.0 diff --git a/cfg/addons/yapb/conf/yapb.cfg b/cfg/addons/yapb/conf/yapb.cfg index 5e6e8ea..19682da 100644 --- a/cfg/addons/yapb/conf/yapb.cfg +++ b/cfg/addons/yapb/conf/yapb.cfg @@ -60,6 +60,13 @@ yb_radio_mode "2" // yb_economics_rounds "1" +// +// Allows bots to ignore economics and buy weapons with disrespect of economics. +// --- +// Default: "25", Min: "0", Max: "100" +// +yb_economics_disrespect_percent "25" + // // Specifies whether bots able to use 'shift' if they thinks that enemy is near. // --- @@ -81,6 +88,13 @@ yb_camping_allowed "1" // yb_avoid_grenades "1" +// +// Allows or disallows bot to check environment for darkness, thus allows or not to use flashlights or NVG. +// --- +// Default: "1", Min: "0", Max: "1" +// +yb_check_darkness "1" + // // Lower bound of time from which time for camping is calculated // --- @@ -193,6 +207,13 @@ yb_ignore_objectives "0" // yb_random_knife_attacks "1" +// +// Maximum number for path length, to predict the enemy. +// --- +// Default: "30", Min: "15", Max: "256" +// +yb_max_nodes_for_predict "30" + // // Enables or disables bots chat functionality. // --- @@ -210,7 +231,7 @@ yb_chat_percent "30" // // Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them. // --- -// Default: "2", Min: "0", Max: "2" +// Default: "2", Min: "0", Max: "3" // yb_shoots_thru_walls "2" @@ -407,9 +428,9 @@ yb_name_prefix "" // // All bots difficulty level. Changing at runtime will affect already created bots. // --- -// Default: "4", Min: "0", Max: "4" +// Default: "3", Min: "0", Max: "4" // -yb_difficulty "4" +yb_difficulty "3" // // Lower bound of random difficulty on bot creation. Only affects newly created bots. -1 means yb_difficulty only used. diff --git a/inc/config.h b/inc/config.h index d1d0ae8..b31e003 100644 --- a/inc/config.h +++ b/inc/config.h @@ -35,6 +35,8 @@ public: int32_t headshotPct {}; int32_t seenThruPct {}; int32_t hearThruPct {}; + int32_t maxRecoil {}; + Vector aimError {}; }; private: diff --git a/inc/engine.h b/inc/engine.h index 5bc97df..60d9cd8 100644 --- a/inc/engine.h +++ b/inc/engine.h @@ -151,6 +151,9 @@ public: // test line void testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr); + // test model + void testModel (const Vector &start, const Vector &end, int hullNumber, edict_t *entToHit, TraceResult *ptr); + // trace line with channel, but allows us to store last traceline bot has fired, saving us some cpu cycles bool testLineChannel (TraceChannel channel, const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult &result); diff --git a/inc/graph.h b/inc/graph.h index 784beb4..4ba137c 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -259,10 +259,6 @@ public: friend class Bot; private: - struct Bucket { - int x, y, z; - }; - int m_editFlags {}; int m_loadAttempts {}; int m_cacheNodeIndex {}; @@ -297,12 +293,13 @@ private: IntArray m_rescuePoints {}; IntArray m_visitedGoals {}; - SmallArray m_buckets[kMaxBucketsInsidePos][kMaxBucketsInsidePos][kMaxBucketsInsidePos]; SmallArray m_matrix {}; SmallArray m_practice {}; SmallArray m_paths {}; SmallArray m_vistable {}; + HashMap , EmptyHash > m_hashTable; + String m_graphAuthor {}; String m_graphModified {}; @@ -327,6 +324,7 @@ public: int getPathDist (int srcIndex, int destIndex); int clearConnections (int index); int getBspSize (); + int locateBucket (const Vector &pos); float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin); @@ -394,9 +392,8 @@ public: const char *getDataDirectory (bool isMemoryFile = false); const char *getOldFormatGraphName (bool isMemoryFile = false); - Bucket locateBucket (const Vector &pos); - IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1); - const SmallArray &getNodesInBucket (const Vector &pos); + IntArray getNarestInRadius (float radius, const Vector &origin, int maxCount = -1); + const IntArray &getNodesInBucket (const Vector &pos); public: size_t getMaxRouteLength () const { @@ -468,6 +465,23 @@ public: edict_t *getEditor () { return m_editor; } + +public: + Path *begin () { + return m_paths.begin (); + } + + Path *begin () const { + return m_paths.begin (); + } + + Path *end () { + return m_paths.end (); + } + + Path *end () const { + return m_paths.end (); + } }; // we're need `bots` diff --git a/inc/yapb.h b/inc/yapb.h index 91ba871..105fb0e 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -467,10 +467,6 @@ constexpr int kGameMaxPlayers = 32; constexpr int kGameTeamNum = 2; constexpr int kInvalidNodeIndex = -1; -constexpr int kMaxBucketSize = static_cast (kMaxNodes * 0.65); -constexpr int kMaxBucketsInsidePos = kMaxNodes * 8 / kMaxBucketSize + 1; -constexpr int kMaxNodesInsideBucket = kMaxBucketSize / kMaxBucketsInsidePos; - // weapon masks constexpr auto kPrimaryWeaponMask = (cr::bit (Weapon::XM1014) | cr::bit (Weapon::M3) | cr::bit (Weapon::MAC10) | cr::bit (Weapon::UMP45) | cr::bit (Weapon::MP5) | cr::bit (Weapon::TMP) | cr::bit (Weapon::P90) | cr::bit (Weapon::AUG) | cr::bit (Weapon::M4A1) | cr::bit (Weapon::SG552) | cr::bit (Weapon::AK47) | cr::bit (Weapon::Scout) | cr::bit (Weapon::SG550) | cr::bit (Weapon::AWP) | cr::bit (Weapon::G3SG1) | cr::bit (Weapon::M249) | cr::bit (Weapon::Famas) | cr::bit (Weapon::Galil)); constexpr auto kSecondaryWeaponMask = (cr::bit (Weapon::P228) | cr::bit (Weapon::Elite) | cr::bit (Weapon::USP) | cr::bit (Weapon::Glock18) | cr::bit (Weapon::Deagle) | cr::bit (Weapon::FiveSeven)); @@ -690,6 +686,7 @@ private: float m_changeViewTime {}; // timestamp to change look at while at freezetime float m_breakableTime {}; // breakeble acquired time float m_jumpDistance {}; // last jump distance + float m_lastBadWeaponSwitchTime {}; // last time we're switched weapon as it's bad bool m_moveToGoal {}; // bot currently moving to goal?? bool m_isStuck {}; // bot is stuck @@ -709,6 +706,7 @@ private: bool m_moveToC4 {}; // ct is moving to bomb bool m_grenadeRequested {}; // bot requested change to grenade bool m_needToSendWelcomeChat {}; // bot needs to greet people on server? + bool m_switchedToKnifeDuringJump {}; // bot needs to revert weapon after jump? Pickup m_pickupType {}; // type of entity which needs to be used/picked up PathWalk m_pathWalk {}; // pointer to current node from path @@ -778,7 +776,6 @@ private: float getEstimatedNodeReachTime (); float isInFOV (const Vector &dest); float getShiftSpeed (); - float getEnemyBodyOffsetCorrection (float distance); float calculateScaleFactor (edict_t *ent); bool canReplaceWeapon (); @@ -809,6 +806,7 @@ private: bool reactOnEnemy (); bool selectBestNextNode (); bool hasAnyWeapons (); + bool isKnifeMode (); bool isDeadlyMove (const Vector &to); bool isOutOfBombTimer (); bool isWeaponBadAtDistance (int weaponIndex, float distance); @@ -830,6 +828,7 @@ private: bool updateLiftHandling (); bool updateLiftStates (); bool canRunHeavyWeight (); + bool isEnemyInSight (Vector &endPos); void doPlayerAvoidance (const Vector &normal); void selectCampButtons (int index); @@ -920,12 +919,13 @@ private: edict_t *lookupBreakable (); edict_t *setCorrectGrenadeVelocity (const char *model); - const Vector &getEnemyBodyOffset (); + Vector getEnemyBodyOffset (); Vector calcThrow (const Vector &start, const Vector &stop); Vector calcToss (const Vector &start, const Vector &stop); Vector isBombAudible (); Vector getBodyOffsetError (float distance); Vector getCampDirection (const Vector &dest); + Vector getCustomHeight (float distance); uint8_t computeMsec (); diff --git a/src/botlib.cpp b/src/botlib.cpp index 9aeff0d..7939e8d 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -44,6 +44,8 @@ ConVar cv_pickup_best ("yb_pickup_best", "1", "Allows or disallows bots to picku ConVar cv_ignore_objectives ("yb_ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and saves hostages."); ConVar cv_random_knife_attacks ("yb_random_knife_attacks", "1", "Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby."); +ConVar cv_max_nodes_for_predict ("yb_max_nodes_for_predict", "30", "Maximum number for path length, to predict the enemy.", true, 15.0f, 256.0f); + // game console variables ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef); ConVar mp_flashlight ("mp_flashlight", nullptr, Var::GameRef); @@ -327,7 +329,7 @@ void Bot::updatePickups () { // this function finds Items to collect or use in the near of a bot // don't try to pickup anything while on ladder or trying to escape from bomb... - if (isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || m_seeEnemyTime + 3.0f > game.time () || !game.isNullEntity (m_enemy) || !bots.hasIntrestingEntities ()) { + if (isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || m_seeEnemyTime + 3.0f < game.time () || !game.isNullEntity (m_enemy) || !bots.hasIntrestingEntities ()) { m_pickupItem = nullptr; m_pickupType = Pickup::None; @@ -543,7 +545,7 @@ void Bot::updatePickups () { // decide to duck or not to duck selectCampButtons (index); - + if (rg.chance (90)) { pushChatterMessage (Chatter::DefendingBombsite); } @@ -758,9 +760,14 @@ Vector Bot::getCampDirection (const Vector &dest) { } if (graph.exists (lookAtWaypoint)) { - return graph[lookAtWaypoint].origin + pev->view_ofs; + return graph[lookAtWaypoint].origin; } } + auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex); + + if (graph.exists (dangerIndex)) { + return graph[dangerIndex].origin; + } return nullptr; } @@ -1548,15 +1555,6 @@ void Bot::updateEmotions () { } void Bot::overrideConditions () { - if (!usesKnife () && m_difficulty >= Difficulty::Normal && ((m_aimFlags & AimFlags::Enemy) || (m_states & Sense::SeeingEnemy)) && !cv_jasonmode.bool_ () && getCurrentTaskId () != Task::Camp && getCurrentTaskId () != Task::SeekCover && !isOnLadder ()) { - m_moveToGoal = false; // don't move to goal - m_navTimeset = game.time (); - - if (util.isPlayer (m_enemy) || (cv_attack_monsters.bool_ () && util.isMonster (m_enemy))) { - attackMovement (); - } - } - // check if we need to escape from bomb if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_notKilled && getCurrentTaskId () != Task::EscapeFromBomb && getCurrentTaskId () != Task::Camp && isOutOfBombTimer ()) { completeTask (); // complete current task @@ -1566,7 +1564,7 @@ void Bot::overrideConditions () { } // special handling, if we have a knife in our hands - if ((bots.getRoundStartTime () + 6.0f > game.time () || !hasAnyWeapons ()) && usesKnife () && (util.isPlayer (m_enemy) || (cv_attack_monsters.bool_ () && util.isMonster (m_enemy)))) { + if ((usesKnife () || !hasAnyWeapons ()) && (util.isPlayer (m_enemy) || (cv_attack_monsters.bool_ () && util.isMonster (m_enemy)))) { float length = pev->origin.distance2d (m_enemy->v.origin); // do waypoint movement if enemy is not reachable with a knife @@ -1574,15 +1572,20 @@ void Bot::overrideConditions () { int nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin); if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex && cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) { - float taskTime = game.time () + length / pev->maxspeed * 0.5f; - if (getCurrentTaskId () != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) { - startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true); + startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + length / (m_moveSpeed * 2.0f), true); } - m_isEnemyReachable = false; - m_enemy = nullptr; - - m_enemyIgnoreTimer = taskTime; + else { + if (getCurrentTaskId () == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) { + clearTask (Task::MoveToPosition); + startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + length / (m_moveSpeed * 2.0f), true); + } + } + } + } + else { + if (length <= 100.0f && (m_states & Sense::SeeingEnemy) && getCurrentTaskId () == Task::MoveToPosition) { + clearTask (Task::MoveToPosition); // remove any move tasks } } } @@ -1596,7 +1599,7 @@ void Bot::overrideConditions () { } // special handling for reloading - if (m_reloadState != Reload::None && m_isReloading && ((pev->button | m_oldButtons) & IN_RELOAD)) { + if (m_reloadState != Reload::None && m_isReloading) { if (m_seeEnemyTime + 4.0f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -1714,7 +1717,7 @@ void Bot::setConditions () { 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)) { + if (graph.exists (nodeIndex) && pathLength < cv_max_nodes_for_predict.int_ () && pev->origin.distanceSq (graph[nodeIndex].origin) > cr::square (384.0f)) { m_aimFlags |= AimFlags::PredictPath; } } @@ -2655,7 +2658,7 @@ void Bot::updateAimDir () { auto pathLength = 0; auto aimNode = findAimingNode (m_lastEnemyOrigin, pathLength); - if (graph.exists (aimNode) && pathLength < kMaxBucketsInsidePos * 2) { + if (graph.exists (aimNode) && pathLength < cv_max_nodes_for_predict.int_ ()) { m_lookAt = graph[aimNode].origin; m_lookAtSafe = m_lookAt; @@ -2688,7 +2691,7 @@ void Bot::updateAimDir () { m_lookAt = m_destOrigin; } } - else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()){ + else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()) { m_lookAt = m_lastEnemyOrigin;; } else { @@ -2821,6 +2824,12 @@ void Bot::frame () { kick (); return; } + + // clear enemy far away + if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::square (2048.0)) { + m_lastEnemy = nullptr; + m_lastEnemyOrigin = nullptr; + } m_slowFrameTimestamp = game.time () + 0.5f; } @@ -2839,7 +2848,7 @@ void Bot::update () { if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { m_hasC4 = !!(pev->weapons & cr::bit (Weapon::C4)); - if (m_hasC4 && cv_ignore_objectives.bool_ ()) { + if (m_hasC4 && (cv_ignore_objectives.bool_ () || cv_jasonmode.bool_ ())) { m_hasC4 = false; } } @@ -2937,7 +2946,7 @@ void Bot::normal_ () { } // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) - if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { + if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasHostage () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { if (rg.chance (40)) { pev->button |= IN_ATTACK; } @@ -2976,7 +2985,7 @@ void Bot::normal_ () { } // reached waypoint is a camp waypoint - if ((m_pathFlags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && cv_camping_allowed.bool_ ()) { + if ((m_pathFlags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && cv_camping_allowed.bool_ () && !isKnifeMode ()) { // check if bot has got a primary weapon and hasn't camped before if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.time () && !hasHostage ()) { @@ -3129,13 +3138,13 @@ void Bot::normal_ () { } } else { - if (!isDucking () && !cr::fequal (m_minSpeed, pev->maxspeed) && m_minSpeed > 1.0f) { + if (!isDucking () && !usesKnife () && !cr::fequal (m_minSpeed, pev->maxspeed) && m_minSpeed > 1.0f) { m_moveSpeed = m_minSpeed; } } float shiftSpeed = getShiftSpeed (); - if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty >= Difficulty::Normal && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !cv_jasonmode.bool_ () && !bots.isBombPlanted ()) { + if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty >= Difficulty::Normal && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !isKnifeMode () && !bots.isBombPlanted ()) { m_moveSpeed = shiftSpeed; } @@ -3248,7 +3257,7 @@ void Bot::huntEnemy_ () { } // bots skill higher than 60? - if (cv_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty >= Difficulty::Normal && !cv_jasonmode.bool_ ()) { + if (cv_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty >= Difficulty::Normal && !isKnifeMode ()) { // then make him move slow if near enemy if (!(m_currentTravelFlags & PathFlag::Jump)) { if (m_currentNodeIndex != kInvalidNodeIndex) { @@ -3359,8 +3368,8 @@ void Bot::attackEnemy_ () { ignoreCollision (); attackMovement (); - if (usesKnife () && !m_lastEnemyOrigin.empty ()) { - m_destOrigin = m_lastEnemyOrigin; + if (usesKnife () && !m_enemyOrigin.empty ()) { + m_destOrigin = m_enemyOrigin; } } else { @@ -3939,7 +3948,7 @@ void Bot::followUser_ () { } m_aimFlags |= AimFlags::Nav; - if (cv_walking_allowed.bool_ () && m_targetEntity->v.maxspeed < m_moveSpeed && !cv_jasonmode.bool_ ()) { + if (cv_walking_allowed.bool_ () && m_targetEntity->v.maxspeed < m_moveSpeed && !isKnifeMode ()) { m_moveSpeed = getShiftSpeed (); } @@ -3957,7 +3966,7 @@ void Bot::followUser_ () { clearSearchNodes (); int destIndex = graph.getNearest (m_targetEntity->v.origin); - auto points = graph.searchRadius (200.0f, m_targetEntity->v.origin); + auto points = graph.getNarestInRadius (200.0f, m_targetEntity->v.origin); for (auto &newIndex : points) { // if waypoint not yet used, assign it as dest @@ -4268,15 +4277,15 @@ void Bot::escapeFromBomb_ () { int lastSelectedGoal = kInvalidNodeIndex, minPathDistance = kInfiniteDistanceLong; float safeRadius = rg.get (1513.0f, 2048.0f); - for (int i = 0; i < graph.length (); ++i) { - if (graph[i].origin.distance (graph.getBombOrigin ()) < safeRadius || isOccupiedNode (i)) { + for (const auto &path : graph) { + if (path.origin.distance (graph.getBombOrigin ()) < safeRadius || isOccupiedNode (path.number)) { continue; } - int pathDistance = graph.getPathDist (m_currentNodeIndex, i); + int pathDistance = graph.getPathDist (m_currentNodeIndex, path.number); if (minPathDistance > pathDistance) { minPathDistance = pathDistance; - lastSelectedGoal = i; + lastSelectedGoal = path.number; } } @@ -4606,8 +4615,7 @@ void Bot::checkSpawnConditions () { } if (m_difficulty >= Difficulty::Normal && rg.chance (m_personality == Personality::Rusher ? 99 : 50) && !m_isReloading && game.mapIs (MapFlags::HostageRescue | MapFlags::Demolition | MapFlags::Escape | MapFlags::Assassination)) { - if (cv_jasonmode.bool_ ()) { - selectSecondary (); + if (isKnifeMode ()) { dropCurrentWeapon (); } else { @@ -4745,7 +4753,7 @@ void Bot::logic () { updateLookAngles (); // and turn to chosen aim direction // the bots wants to fire at something? - if (m_shootAtDeadTime > game.time () || (m_wantsToFire && !m_isUsingGrenade && (usesKnife () || m_shootTime <= game.time ()))) { + if (m_shootAtDeadTime > game.time () || (m_wantsToFire && !m_isUsingGrenade && (m_shootTime <= game.time ()))) { fireWeapons (); // if bot didn't fire a bullet try again next frame } @@ -4794,7 +4802,7 @@ void Bot::logic () { } // ensure we're not stuck destroying/picking something - if (m_navTimeset + getEstimatedNodeReachTime () - m_frameInterval * 2.0f < game.time () && !(m_states & Sense::SeeingEnemy)) { + if (m_navTimeset + getEstimatedNodeReachTime () + 2.0f < game.time () && !(m_states & Sense::SeeingEnemy)) { ensureEntitiesClear (); } translateInput (); @@ -4848,6 +4856,10 @@ void Bot::showDebugOverlay () { static HashMap personalities; static HashMap flags; + auto boolValue = [] (const bool test) { + return test ? "Yes" : "No"; + }; + if (tasks.empty ()) { tasks[Task::Normal] = "Normal"; tasks[Task::Pause] = "Pause"; @@ -4919,7 +4931,7 @@ void Bot::showDebugOverlay () { auto weapon = util.weaponIdToAlias (m_currentWeapon); String debugData; - debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", pev->netname.chars (), m_healthValue, pev->armorvalue, taskID, tasks[taskID], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality]); + debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s Terrain=%s Stuck=%s\n", pev->netname.chars (), m_healthValue, pev->armorvalue, taskID, tasks[taskID], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality], boolValue (m_checkTerrain), boolValue (m_isStuck)); MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, overlayEntity) .writeByte (TE_TEXTMESSAGE) @@ -4957,7 +4969,7 @@ void Bot::showDebugOverlay () { } bool Bot::hasHostage () { - if (cv_ignore_objectives.bool_ () || !game.mapIs (MapFlags::Demolition)) { + if (cv_ignore_objectives.bool_ () || game.mapIs (MapFlags::Demolition)) { return false; } @@ -4983,7 +4995,7 @@ int Bot::getAmmo (int id) { const auto &prop = conf.getWeaponProp (id); if (prop.ammo1 == -1 || prop.ammo1 > kMaxWeapons - 1) { - return 0; + return -1; } return m_ammo[prop.ammo1]; } @@ -5500,7 +5512,7 @@ void Bot::updateHearing () { // did the bot hear someone ? if (player != nullptr && util.isPlayer (player)) { // change to best weapon if heard something - if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !cv_jasonmode.bool_ ()) { + if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !isKnifeMode ()) { selectBestWeapon (); } diff --git a/src/combat.cpp b/src/combat.cpp index 575f52c..2ae1071 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -245,6 +245,7 @@ bool Bot::lookupEnemies () { newEnemy = player; } } + auto distanceScale = usesKnife () ? 1.0f : 0.75f; // the old enemy is no longer visible or if (game.isNullEntity (newEnemy)) { @@ -271,7 +272,7 @@ bool Bot::lookupEnemies () { float scaleFactor = (1.0f / calculateScaleFactor (intresting)); float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor; - if (distance * 0.65f < nearestDistance) { + if (distance * distanceScale < nearestDistance) { nearestDistance = distance; newEnemy = intresting; } @@ -304,7 +305,7 @@ bool Bot::lookupEnemies () { } float distance = player->v.origin.distanceSq (pev->origin); - if (distance * 0.65f < nearestDistance) { + if (distance * distanceScale < nearestDistance) { nearestDistance = distance; newEnemy = player; @@ -315,8 +316,8 @@ bool Bot::lookupEnemies () { } } } - m_enemyUpdateTime = cr::clamp (game.time () + m_frameInterval * 25.0f, 0.5f, 0.75f); - + m_enemyUpdateTime = game.time () + cr::clamp (m_frameInterval * 3.0f, 0.2f, 0.64f); + if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) { newEnemy = shieldEnemy; } @@ -445,16 +446,20 @@ Vector Bot::getBodyOffsetError (float distance) { } if (m_aimErrorTime < game.time ()) { - const float error = distance / (cr::clamp (static_cast (m_difficulty), 1.0f, 4.0f) * 1000.0f); + const float hitError = distance / (cr::clamp (m_difficulty, 1, 4) * 1000.0f); auto &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins; - m_aimLastError = Vector (rg.get (mins.x * error, maxs.x * error), rg.get (mins.y * error, maxs.y * error), rg.get (mins.z * error, maxs.z * error)); - m_aimErrorTime = game.time () + rg.get (1.0f, 1.2f); + m_aimLastError = Vector (rg.get (mins.x * hitError, maxs.x * hitError), rg.get (mins.y * hitError, maxs.y * hitError), rg.get (mins.z * hitError, maxs.z * hitError)); + + auto &aimError = conf.getDifficultyTweaks (m_difficulty) ->aimError; + m_aimLastError += Vector (rg.get (-aimError.x, aimError.x), rg.get (-aimError.y, aimError.y), rg.get (-aimError.z, aimError.z)); + + m_aimErrorTime = game.time () + rg.get (1.5f, 2.0f); } return m_aimLastError; } -const Vector &Bot::getEnemyBodyOffset () { + Vector Bot::getEnemyBodyOffset () { // the purpose of this function, is to make bot aiming not so ideal. it's mutate m_enemyOrigin enemy vector // returned from visibility check function. @@ -474,11 +479,15 @@ const Vector &Bot::getEnemyBodyOffset () { m_enemyParts &= ~Visibility::Head; } + const auto headOffset = [] (edict_t *e) { + return e->v.absmin.z + e->v.size.z * 0.81f; + }; + Vector spot = m_enemy->v.origin; - Vector compensation = 1.0f * m_frameInterval * m_enemy->v.velocity - 1.0f * m_frameInterval * pev->velocity; + Vector compensation = nullptr; if (!usesSniper () && distance > kDoubleSprayDistance) { - compensation *= m_frameInterval; + compensation = (m_enemy->v.velocity - pev->velocity) * m_frameInterval * 2.0f; compensation.z = 0.0f; } else { @@ -490,71 +499,67 @@ const Vector &Bot::getEnemyBodyOffset () { spot += getBodyOffsetError (distance); } else if (util.isPlayer (m_enemy)) { - 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.25f); - - // reduce headshot percent in case we're play too good - if (!onLoosingStreak) { - headshotPct = cr::abs (headshotPct - headshotPct / 4); - } // now check is our skill match to aim at head, else aim at enemy body if (rg.chance (headshotPct)) { - if (onLoosingStreak) { - spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance)); - } - else { - spot = m_enemyOrigin; - } + spot = m_enemyOrigin + getCustomHeight (distance); } else { - spot.z += highOffset; + spot = m_enemy->v.origin; + spot.z -= 3.0f; } } else if (m_enemyParts & Visibility::Body) { - spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance)); + spot = m_enemy->v.origin; } else if (m_enemyParts & Visibility::Other) { spot = m_enemyOrigin; } else if (m_enemyParts & Visibility::Head) { - spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance)); + spot = m_enemyOrigin + getCustomHeight (distance); } } + Vector newSpot = spot; - m_enemyOrigin = spot + compensation; - m_lastEnemyOrigin = spot + compensation; + if (m_difficulty < Difficulty::Expert && isEnemyInSight (newSpot)) { + spot = newSpot + ((spot - newSpot) * 0.01f); // gradually adjust the aiming direction + } + spot += compensation; + + if (usesKnife () && m_difficulty >= Difficulty::Normal) { + spot.z = headOffset (m_enemy); + } + m_lastEnemyOrigin = spot; // add some error to unskilled bots if (m_difficulty < Difficulty::Hard) { - m_enemyOrigin += getBodyOffsetError (distance); + spot += getBodyOffsetError (distance); } - return m_enemyOrigin; + return spot; } -float Bot::getEnemyBodyOffsetCorrection (float distance) { +Vector Bot::getCustomHeight (float distance) { enum DistanceIndex { Long, Middle, Short }; - static float offsetRanges[9][3] = { + static constexpr float offsetRanges[9][3] = { { 0.0f, 0.0f, 0.0f }, // none - { 0.0f, 0.0f, 4.0f }, // melee - { 3.5f, 2.0f, 1.5f }, // pistol - { 9.5f, 5.0f, -8.0f }, // shotgun - { 4.5f -3.5f, -5.0f }, // zoomrifle - { 5.5f, -3.0f, -5.5f }, // rifle - { 5.5f, -2.5f, -5.5f }, // smg - { 3.5f, 1.5f, -6.0f }, // sniper - { 2.5f, -4.0f, -9.0f } // heavy + { 0.0f, 0.0f, 0.0f }, // melee + { 1.5f, -3.0f, -4.5f }, // pistol + { 6.5f, 6.0f, -2.0f }, // shotgun + { 2.5f -7.5f, -9.5f }, // zoomrifle + { 2.5f, -7.5f, -9.5f }, // rifle + { 2.5f, -7.5f, -9.5f }, // smg + { 0.0f, -2.5f, -6.0f }, // sniper + { 1.5f, -4.0f, -9.0f } // heavy }; // only highskilled bots do that - if (m_difficulty != Difficulty::Expert) { + if (m_difficulty != Difficulty::Expert || (m_enemy->v.flags & FL_DUCKING)) { return 0.0f; } @@ -568,7 +573,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { else if (distance > kSprayDistance && distance <= kDoubleSprayDistance) { distanceIndex = DistanceIndex::Middle; } - return offsetRanges[m_weaponType][distanceIndex]; + return { 0.0f, 0.0f, offsetRanges[m_weaponType][distanceIndex] }; } bool Bot::isFriendInLineOfFire (float distance) { @@ -742,7 +747,7 @@ bool Bot::isPenetrableObstacle3 (const Vector &dest) { bool Bot::needToPauseFiring (float distance) { // returns true if bot needs to pause between firing to compensate for punchangle & weapon spread - if (usesSniper ()) { + if (usesSniper () || m_isUsingGrenade) { return false; } @@ -761,22 +766,21 @@ bool Bot::needToPauseFiring (float distance) { return false; } else if (distance < kDoubleSprayDistance) { - offset = 8.0f; + offset = 2.75f; } - const float xPunch = cr::deg2rad (pev->punchangle.x); - const float yPunch = cr::deg2rad (pev->punchangle.y); + const float xPunch = cr::square (cr::deg2rad (pev->punchangle.x)); + const float yPunch = cr::square (cr::deg2rad (pev->punchangle.y)); const float interval = m_frameInterval; const float tolerance = (100.0f - static_cast (m_difficulty) * 25.0f) / 99.0f; + const float baseTime = distance > kDoubleSprayDistance ? 0.65f : 0.48f; + const float maxRecoil = static_cast (conf.getDifficultyTweaks (m_difficulty)->maxRecoil); // check if we need to compensate recoil - if (cr::tanf (cr::sqrtf (cr::abs (xPunch * xPunch) + cr::abs (yPunch * yPunch))) * distance > offset + 30.0f + tolerance) { + if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) { if (m_firePause < game.time ()) { - m_firePause = rg.get (0.65f, 0.65f + 0.3f * tolerance); + m_firePause = game.time () + rg.get (baseTime, baseTime + maxRecoil * 0.01f * tolerance) - interval; } - m_firePause -= interval; - m_firePause += game.time (); - return true; } return false; @@ -874,10 +878,10 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } // need to care for burst fire? - if (distance < kSprayDistance || m_blindTime > game.time ()) { + if (distance < kSprayDistance || m_blindTime > game.time () || usesKnife ()) { if (id == Weapon::Knife) { - if (distance < 64.0f) { - if (rg.chance (30) || hasShield ()) { + if (distance < 72.0f) { + if (rg.chance (40) || hasShield ()) { pev->button |= IN_ATTACK; // use primary attack } else { @@ -901,16 +905,16 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { m_shootTime = game.time (); } else { - if (needToPauseFiring (distance)) { - return; - } - // don't attack with knife over long distance if (id == Weapon::Knife) { m_shootTime = game.time (); return; } + if (needToPauseFiring (distance)) { + return; + } + if (tab[choosen].primaryFireHold) { m_shootTime = game.time (); m_zoomCheckTime = game.time (); @@ -922,8 +926,8 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { pev->button |= IN_ATTACK; } - const float minDelay[] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.6f }; - const float maxDelay[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f }; + constexpr float minDelay[] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.6f }; + constexpr float maxDelay[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f }; const int offset = cr::abs (m_difficulty * 25 / 20 - 5); @@ -935,26 +939,29 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { void Bot::fireWeapons () { // this function will return true if weapon was fired, false otherwise - + // + // do not handle this if with grenade, as it's done it throw grendate task + if (m_isUsingGrenade) { + return; + } float distance = m_lookAt.distance (getEyesPos ()); // how far away is the enemy? // or if friend in line of fire, stop this too but do not update shoot time - if (!game.isNullEntity (m_enemy)) { - if (isFriendInLineOfFire (distance)) { - m_fightStyle = Fight::Strafe; - m_lastFightStyleCheck = game.time (); + if (isFriendInLineOfFire (distance)) { + m_fightStyle = Fight::Strafe; + m_lastFightStyleCheck = game.time (); - return; - } + return; } + auto tab = conf.getRawWeapons (); - edict_t *enemy = m_enemy; + auto enemy = m_enemy; int selectId = Weapon::Knife, selectIndex = 0, choosenWeapon = 0; int weapons = pev->weapons; // if jason mode use knife only - if (cv_jasonmode.bool_ ()) { + if (isKnifeMode ()) { selectWeapons (distance, selectIndex, selectId, choosenWeapon); return; } @@ -997,7 +1004,7 @@ void Bot::fireWeapons () { m_reloadState = Reload::Primary; m_reloadCheckTime = game.time (); - if (rg.chance (cr::abs (m_difficulty * 25 - 100)) && rg.chance (15)) { + if (rg.chance (cr::abs (m_difficulty * 25 - 100)) && rg.chance (20)) { pushRadioMessage (Radio::NeedBackup); } } @@ -1015,30 +1022,35 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) { // this function checks, is it better to use pistol instead of current primary weapon // to attack our enemy, since current weapon is not very good in this situation. - auto &info = conf.getWeapons (); + // do not switch weapons when crossing the distance line + if (m_lastBadWeaponSwitchTime + 3.0f < game.time ()) { + auto &info = conf.getWeapons (); - if (m_difficulty < Difficulty::Normal || !hasSecondaryWeapon ()) { - return false; - } - auto weaponType = info[weaponIndex].type; + if (m_difficulty < Difficulty::Normal || !hasSecondaryWeapon ()) { + return false; + } + auto weaponType = info[weaponIndex].type; - if (weaponType == WeaponType::Melee) { - return false; - } + if (weaponType == WeaponType::Melee) { + return false; + } - // check is ammo available for secondary weapon - if (m_ammoInClip[info[bestSecondaryCarried ()].id] >= 3) { - return false; - } + // check is ammo available for secondary weapon + if (m_ammoInClip[info[bestSecondaryCarried ()].id] >= 3) { + return false; + } - // better use pistol in short range distances, when using sniper weapons - if (weaponType == WeaponType::Sniper && distance < 450.0f) { - return true; - } + // better use pistol in short range distances, when using sniper weapons + if (weaponType == WeaponType::Sniper && distance < 400.0f) { + m_lastBadWeaponSwitchTime = game.time (); + return true; + } - // shotguns is too inaccurate at long distances, so weapon is bad - if (weaponType == WeaponType::Shotgun && distance > 750.0f) { - return true; + // shotguns is too inaccurate at long distances, so weapon is bad + if (weaponType == WeaponType::Shotgun && distance > 750.0f) { + m_lastBadWeaponSwitchTime = game.time (); + return true; + } } return false; } @@ -1051,7 +1063,7 @@ void Bot::focusEnemy () { // aim for the head and/or body m_lookAt = getEnemyBodyOffset (); - if (m_enemySurpriseTime > game.time () && !usesKnife ()) { + if (m_enemySurpriseTime > game.time ()) { return; } float distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum? @@ -1061,9 +1073,6 @@ void Bot::focusEnemy () { if (distance < 80.0f) { m_wantsToFire = true; } - else if (distance > 120.0f) { - m_wantsToFire = false; - } } else { m_wantsToFire = true; @@ -1100,6 +1109,11 @@ void Bot::attackMovement () { return; } + // use enemy as dest origin if with knife + if (usesKnife ()) { + m_destOrigin = m_enemy->v.origin; + } + if (m_lastUsedNodesTime - m_frameInterval > game.time ()) { return; } @@ -1136,10 +1150,6 @@ void Bot::attackMovement () { 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; @@ -1167,7 +1177,7 @@ void Bot::attackMovement () { } } } - else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75)) { + else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75) || usesKnife ()) { m_fightStyle = Fight::Strafe; } else { @@ -1181,6 +1191,13 @@ void Bot::attackMovement () { m_lastFightStyleCheck = game.time (); } + if (distance < 96.0f && !usesKnife ()) { + m_moveSpeed = -pev->maxspeed; + } + else if (usesKnife ()) { + m_fightStyle = Fight::None; + } + if (m_fightStyle == Fight::Strafe) { auto swapStrafeCombatDir = [&] () { m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); @@ -1243,8 +1260,13 @@ void Bot::attackMovement () { pev->button |= IN_JUMP; } } - else { - if ((m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { + else if (m_fightStyle == Fight::Stay) { + const bool alreadyDucking = m_duckTime > game.time () || isDucking (); + + if (alreadyDucking) { + m_duckTime = game.time () + m_frameInterval * 2.0f; + } + else if ((distance > kDoubleSprayDistance && hasPrimaryWeapon ()) && (m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); if (graph.isVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { @@ -1255,10 +1277,6 @@ void Bot::attackMovement () { m_strafeSpeed = 0.0f; } - m_navTimeset = game.time (); - m_moveToGoal = false; - m_checkTerrain = false; - if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) { if (distance < 768.0f) { if (rg.get (0, 1000) < rg.get (7, 12) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { @@ -1465,7 +1483,11 @@ bool Bot::rateGroundWeapon (edict_t *ent) { } bool Bot::hasAnyWeapons () { - return (pev->weapons & (kPrimaryWeaponMask | kSecondaryWeaponMask)); + return !!(pev->weapons & (kPrimaryWeaponMask | kSecondaryWeaponMask)); +} + +bool Bot::isKnifeMode () { + return cv_jasonmode.bool_ () || (usesKnife () && !hasAnyWeapons ()); } void Bot::selectBestWeapon () { @@ -1473,7 +1495,7 @@ void Bot::selectBestWeapon () { // current weapon to best one. // if knife mode activated, force bot to use knife - if (cv_jasonmode.bool_ ()) { + if (isKnifeMode ()) { selectWeaponById (Weapon::Knife); return; } @@ -1649,7 +1671,7 @@ void Bot::checkReload () { bool uninterruptibleTask = (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::PickupItem || task == Task::ThrowExplosive || task == Task::ThrowFlashbang || task == Task::ThrowSmoke); // do not check for reload - if (uninterruptibleTask || m_isUsingGrenade) { + if (uninterruptibleTask || m_isUsingGrenade || usesKnife ()) { m_reloadState = Reload::None; return; } @@ -1849,7 +1871,7 @@ void Bot::checkGrenadesThrow () { }; // check if throwing a grenade is a good thing to do... - if (preventibleTasks || isInNarrowPlace () || cv_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || cv_jasonmode.bool_ () || (m_grenadeRequested || m_grenadeCheckTime >= game.time ())) { + if (preventibleTasks || isInNarrowPlace () || cv_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || (isKnifeMode () && !bots.isBombPlanted ()) || (m_grenadeRequested || m_grenadeCheckTime >= game.time ())) { clearThrowStates (m_states); return; } @@ -1915,7 +1937,7 @@ void Bot::checkGrenadesThrow () { if (radius < 164.0f) { radius = 164.0f; } - auto predicted = graph.searchRadius (radius, pos, 12); + auto predicted = graph.getNarestInRadius (radius, pos, 12); if (predicted.empty ()) { m_states &= ~Sense::ThrowExplosive; @@ -2029,3 +2051,14 @@ void Bot::checkGrenadesThrow () { clearThrowStates (m_states); } } + +bool Bot::isEnemyInSight (Vector &endPos) { + TraceResult aimHitTr {}; + game.testModel (getEyesPos (), getEyesPos () + pev->v_angle.forward () * kInfiniteDistance, 0, m_enemy, &aimHitTr); + + if (aimHitTr.pHit != m_enemy) { + return false; + } + endPos = aimHitTr.vecEndPos; + return true; +} diff --git a/src/config.cpp b/src/config.cpp index 26d4b5c..bbdb285 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -20,6 +20,7 @@ BotConfig::BotConfig () { void BotConfig::loadConfigs () { setupMemoryFiles (); + loadCustomConfig (); loadNamesConfig (); loadChatConfig (); loadChatterConfig (); @@ -28,7 +29,6 @@ void BotConfig::loadConfigs () { loadLogosConfig (); loadAvatarsConfig (); loadDifficultyConfig (); - loadCustomConfig (); } void BotConfig::loadMainConfig (bool isFirstLoad) { @@ -533,27 +533,27 @@ void BotConfig::loadDifficultyConfig () { // initialize defaults m_difficulty[Difficulty::Noob] = { - { 0.8f, 1.0f }, 5, 0, 0 + { 0.8f, 1.0f }, 5, 0, 0, 38, { 30.0f, 30.0f, 40.0f } }; m_difficulty[Difficulty::Easy] = { - { 0.6f, 0.8f }, 30, 10, 10 + { 0.6f, 0.8f }, 30, 10, 10, 32, { 15.0f, 15.0f, 24.0f } }; m_difficulty[Difficulty::Normal] = { - { 0.4f, 0.6f }, 50, 30, 40 + { 0.4f, 0.6f }, 50, 30, 40, 26, { 5.0f, 5.0f, 10.0f } }; m_difficulty[Difficulty::Hard] = { - { 0.2f, 0.4f }, 75, 60, 70 + { 0.2f, 0.4f }, 75, 60, 70, 23, { 0.0f, 0.0f, 0.0f } }; m_difficulty[Difficulty::Expert] = { - { 0.1f, 0.2f }, 100, 90, 90 + { 0.1f, 0.2f }, 100, 90, 90, 21, { 0.0f, 0.0f, 0.0f } }; - // currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob - constexpr uint32_t kMaxDifficultyValues = 5; + // currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob, recoil, aim_error {x,y,z} + constexpr uint32_t kMaxDifficultyValues = 9; // helper for parsing each level auto parseLevel = [&] (int32_t level, StringRef data) { @@ -570,6 +570,10 @@ void BotConfig::loadDifficultyConfig () { diff->headshotPct = values[2].int_ (); diff->seenThruPct = values[3].int_ (); diff->hearThruPct = values[4].int_ (); + diff->maxRecoil = values[5].int_ (); + diff->aimError.x = values[6].float_ (); + diff->aimError.y = values[7].float_ (); + diff->aimError.z = values[8].float_ (); }; // avatars inititalization diff --git a/src/control.cpp b/src/control.cpp index bdae38b..2b3ecba 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -589,7 +589,7 @@ int BotControl::cmdNodeClean () { if (strValue (option) == "all") { int removed = 0; - for (int i = 0; i < graph.length (); ++i) { + for (auto i = 0; i < graph.length (); ++i) { removed += graph.clearConnections (i); } msg ("Done. Processed %d nodes. %d useless paths was cleared.", graph.length (), removed); @@ -842,9 +842,9 @@ int BotControl::cmdNodeIterateCamp () { } } else if (op == "begin") { - for (int i = 0; i < graph.length (); ++i) { - if (graph[i].flags & NodeFlag::Camp) { - m_campIterator.push (i); + for (const auto &path : graph) { + if (path.flags & NodeFlag::Camp) { + m_campIterator.push (path.number); } } if (!m_campIterator.empty ()) { @@ -877,8 +877,8 @@ int BotControl::cmdAdjustHeight () { auto heightOffset = floatValue (offset); // adjust the height for all the nodes (negative values possible) - for (int i = 0; i < graph.length (); ++i) { - graph[i].origin.z += heightOffset; + for (auto &path : graph) { + path.origin.z += heightOffset; } return BotCommandResult::Handled; } diff --git a/src/engine.cpp b/src/engine.cpp index 7c6c990..f1617d2 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -195,6 +195,10 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w .writeByte (speed); // speed } +void Game::testModel (const Vector &start, const Vector &end, int hullNumber, edict_t *entToHit, TraceResult *ptr) { + engfuncs.pfnTraceModel (start, end, hullNumber, entToHit, ptr); +} + void Game::testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr) { // this function traces a line dot by dot, starting from vecStart in the direction of vecEnd, // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops diff --git a/src/graph.cpp b/src/graph.cpp index 9ca4fad..6ab5777 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -451,7 +451,7 @@ int BotGraph::getFarest (const Vector &origin, float maxDistance) { maxDistance = cr::square (maxDistance); for (const auto &path : m_paths) { - float distance = path.origin.distanceSq (origin); + const float distance = path.origin.distanceSq (origin); if (distance > maxDistance) { index = path.number; @@ -472,7 +472,7 @@ int BotGraph::getNearestNoBuckets (const Vector &origin, float minDistance, int if (flags != -1 && !(path.flags & flags)) { continue; // if flag not -1 and node has no this flag, skip node } - float distance = path.origin.distanceSq (origin); + const float distance = path.origin.distanceSq (origin); if (distance < minDistance) { index = path.number; @@ -492,9 +492,12 @@ int BotGraph::getEditorNearest () { int BotGraph::getNearest (const Vector &origin, float minDistance, int flags) { // find the nearest node to that origin and return the index - auto &bucket = getNodesInBucket (origin); + if (minDistance > 256.0f && !cr::fequal (minDistance, kInfiniteDistance)) { + return getNearestNoBuckets (origin, minDistance, flags); + } + const auto &bucket = getNodesInBucket (origin); - if (bucket.empty ()) { + if (bucket.length () < kMaxNodeLinks) { return getNearestNoBuckets (origin, minDistance, flags); } @@ -520,17 +523,26 @@ int BotGraph::getNearest (const Vector &origin, float minDistance, int flags) { return index; } -IntArray BotGraph::searchRadius (float radius, const Vector &origin, int maxCount) { +IntArray BotGraph::getNarestInRadius (float radius, const Vector &origin, int maxCount) { // returns all nodes within radius from position + radius = cr::square (radius); + IntArray result; const auto &bucket = getNodesInBucket (origin); - if (bucket.empty ()) { - result.push (getNearestNoBuckets (origin, radius)); + if (bucket.length () < kMaxNodeLinks || radius > cr::square (256.0f)) { + for (const auto &path : m_paths) { + if (maxCount != -1 && static_cast (result.length ()) > maxCount) { + break; + } + + if (origin.distanceSq (path.origin) < radius) { + result.push (path.number); + } + } return result; } - radius = cr::square (radius); for (const auto &at : bucket) { if (maxCount != -1 && static_cast (result.length ()) > maxCount) { @@ -3010,24 +3022,19 @@ BotGraph::BotGraph () { } void BotGraph::initBuckets () { - for (int x = 0; x < kMaxBucketsInsidePos; ++x) { - for (int y = 0; y < kMaxBucketsInsidePos; ++y) { - for (int z = 0; z < kMaxBucketsInsidePos; ++z) { - m_buckets[x][y][z].reserve (kMaxNodesInsideBucket); - m_buckets[x][y][z].clear (); - } - } - } + m_hashTable.clear (); } void BotGraph::addToBucket (const Vector &pos, int index) { - const auto &bucket = locateBucket (pos); - m_buckets[bucket.x][bucket.y][bucket.z].push (index); + m_hashTable[locateBucket (pos)].emplace (index); +} + +const Array &BotGraph::getNodesInBucket (const Vector &pos) { + return m_hashTable[locateBucket (pos)]; } void BotGraph::eraseFromBucket (const Vector &pos, int index) { - const auto &bucket = locateBucket (pos); - auto &data = m_buckets[bucket.x][bucket.y][bucket.z]; + auto &data = m_hashTable[locateBucket (pos)]; for (size_t i = 0; i < data.length (); ++i) { if (data[i] == index) { @@ -3037,19 +3044,13 @@ void BotGraph::eraseFromBucket (const Vector &pos, int index) { } } -BotGraph::Bucket BotGraph::locateBucket (const Vector &pos) { - constexpr auto size = static_cast (kMaxNodes * 2); +int BotGraph::locateBucket (const Vector &pos) { + constexpr auto width = cr::square (kMaxNodes); - return { - cr::abs (static_cast ((pos.x + size) / kMaxBucketSize)), - cr::abs (static_cast ((pos.y + size) / kMaxBucketSize)), - cr::abs (static_cast ((pos.z + size) / kMaxBucketSize)) + auto hash = [&] (float axis, int32_t shift) { + return ((static_cast (axis) + width) & 0x007f80) >> shift; }; -} - -const SmallArray &BotGraph::getNodesInBucket (const Vector &pos) { - const auto &bucket = locateBucket (pos); - return m_buckets[bucket.x][bucket.y][bucket.z]; + return hash (pos.x, 15) + hash (pos.y, 7); } void BotGraph::updateGlobalPractice () { diff --git a/src/manager.cpp b/src/manager.cpp index 8dce9e4..d367980 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -21,7 +21,7 @@ ConVar cv_join_team ("yb_join_team", "any", "Forces all bots to join team specif ConVar cv_join_delay ("yb_join_delay", "5.0", "Specifies after how many seconds bots should start to join the game after the changelevel.", true, 0.0f, 30.0f); ConVar cv_name_prefix ("yb_name_prefix", "", "All the bot names will be prefixed with string specified with this cvar.", false); -ConVar cv_difficulty ("yb_difficulty", "4", "All bots difficulty level. Changing at runtime will affect already created bots.", true, 0.0f, 4.0f); +ConVar cv_difficulty ("yb_difficulty", "3", "All bots difficulty level. Changing at runtime will affect already created bots.", true, 0.0f, 4.0f); ConVar cv_difficulty_min ("yb_difficulty_min", "-1", "Lower bound of random difficulty on bot creation. Only affects newly created bots. -1 means yb_difficulty only used.", true, -1.0f, 4.0f); ConVar cv_difficulty_max ("yb_difficulty_max", "-1", "Upper bound of random difficulty on bot creation. Only affects newly created bots. -1 means yb_difficulty only used.", true, -1.0f, 4.0f); @@ -1285,6 +1285,7 @@ void Bot::newRound () { m_isLeader = false; m_hasProgressBar = false; m_canChooseAimDirection = true; + m_switchedToKnifeDuringJump = false; m_preventFlashing = 0.0f; m_timeTeamOrder = 0.0f; @@ -1410,6 +1411,7 @@ void Bot::newRound () { m_combatStrafeDir = Dodge::None; m_fightStyle = Fight::None; m_lastFightStyleCheck = 0.0f; + m_lastBadWeaponSwitchTime = 0.0f; m_checkWeaponSwitch = true; m_checkKnifeSwitch = true; diff --git a/src/navigate.cpp b/src/navigate.cpp index 8286e5b..7761a41 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -103,7 +103,7 @@ int Bot::findBestGoal () { else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) { // send some terrorists to guard planted bomb if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0f) { - return pushToHistroy (m_chosenGoalIndex = graph.getNearest (graph.getBombOrigin ())); + return pushToHistroy (m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ())); } } else if (game.mapIs (MapFlags::Escape)) { @@ -844,7 +844,7 @@ bool Bot::updateNavigation () { m_desiredVelocity = nullptr; } } - else if (!cv_jasonmode.bool_ () && usesKnife () && isOnFloor ()) { + else if (!isKnifeMode () && m_switchedToKnifeDuringJump) { selectBestWeapon (); // if jump distance was big enough, cooldown a little @@ -852,6 +852,7 @@ bool Bot::updateNavigation () { startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.45f, false); } m_jumpDistance = 0.0f; + m_switchedToKnifeDuringJump = false; } } @@ -1041,7 +1042,7 @@ bool Bot::updateNavigation () { desiredDistance = 0.0f; } else if (isOccupiedNode (m_path->number)) { - desiredDistance = 72.0f; + desiredDistance = 96.0f; } else { desiredDistance = m_path->radius; @@ -1601,16 +1602,16 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: }; // square distance heuristic with hostages - auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float { - if (graph[startIndex].flags & NodeFlag::NoHostage) { + auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int, int goalIndex) -> float { + if (graph[index].flags & NodeFlag::NoHostage) { return 65355.0f; } - return hfunctionPathDist (index, startIndex, goalIndex); + return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex); }; // none heuristic - auto hfunctionNone = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float { - return hfunctionPathDist (index, startIndex, goalIndex) / (128.0f * 10.0f); + auto hfunctionNone = [&hfunctionPathDist] (int index, int, int goalIndex) -> float { + return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex) / (128.0f * 10.0f); }; if (!graph.exists (srcIndex)) { @@ -1673,7 +1674,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: // put start node into open list srcRoute->g = gcalc (m_team, srcIndex, kInvalidNodeIndex); - srcRoute->f = srcRoute->g + hcalc (srcIndex, srcIndex, destIndex); + srcRoute->f = srcRoute->g + hcalc (srcIndex, kInvalidNodeIndex, destIndex); srcRoute->state = RouteState::Open; m_routeQue.clear (); @@ -1724,7 +1725,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: // calculate the F value as F = G + H const float g = curRoute->g + gcalc (m_team, child.index, currentIndex); - const float h = hcalc (child.index, srcIndex, destIndex); + const float h = hcalc (child.index, kInvalidNodeIndex, destIndex); const float f = g + h; if (childRoute->state == RouteState::New || childRoute->f > f) { @@ -1747,14 +1748,13 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: void Bot::clearSearchNodes () { m_pathWalk.clear (); - m_chosenGoalIndex = kInvalidNodeIndex; } void Bot::clearRoute () { m_routes.resize (static_cast (graph.length ())); - for (int i = 0; i < graph.length (); ++i) { - auto route = &m_routes[i]; + for (const auto &path : graph) { + auto route = &m_routes[path.number]; route->g = route->f = 0.0f; route->parent = kInvalidNodeIndex; @@ -1801,28 +1801,25 @@ 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. - int busy = kInvalidNodeIndex; + int busyIndex = kInvalidNodeIndex; float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance }; int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex , kInvalidNodeIndex }; - auto &bucket = graph.getNodesInBucket (pev->origin); - int numToSkip = cr::clamp (rg.get (0, 2), 0, static_cast (bucket.length () / 2)); + const auto &origin = pev->origin + pev->velocity * m_frameInterval; + const auto &bucket = graph.getNodesInBucket (origin); - for (const int at : bucket) { - bool skip = !!(at == m_currentNodeIndex); + for (const auto &i : bucket) { + const auto &path = graph[i]; - // skip the current node, if any - if (skip && numToSkip > 0) { + if (!graph.exists (path.number)) { continue; } + bool skip = !!(path.number == m_currentNodeIndex); - // skip current and recent previous nodes - for (int j = 0; j < numToSkip; ++j) { - if (at == m_previousNodes[j]) { - skip = true; - break; - } + // skip current or recent previous node + if (path.number == m_previousNodes[0]) { + skip = true; } // skip node from recent list @@ -1831,28 +1828,28 @@ bool Bot::findNextBestNode () { } // cts with hostages should not pick nodes with no hostage flag - if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[at].flags & NodeFlag::NoHostage) && hasHostage ()) { + if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[path.number].flags & NodeFlag::NoHostage) && hasHostage ()) { continue; } // check we're have link to it - if (m_currentNodeIndex != kInvalidNodeIndex && !graph.isConnected (m_currentNodeIndex, at)) { + if (m_currentNodeIndex != kInvalidNodeIndex && !graph.isConnected (m_currentNodeIndex, path.number)) { continue; } // ignore non-reacheable nodes... - if (!isReachableNode (at)) { + if (!isReachableNode (path.number)) { continue; } // check if node is already used by another bot... - if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (at)) { - busy = at; + if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (path.number)) { + busyIndex = path.number; continue; } // if we're still here, find some close nodes - float distance = pev->origin.distanceSq (graph[at].origin); + float distance = pev->origin.distanceSq (path.origin); if (distance < lessDist[0]) { lessDist[2] = lessDist[1]; @@ -1862,18 +1859,18 @@ bool Bot::findNextBestNode () { lessIndex[1] = lessIndex[0]; lessDist[0] = distance; - lessIndex[0] = at; + lessIndex[0] = path.number; } else if (distance < lessDist[1]) { lessDist[2] = lessDist[1]; lessIndex[2] = lessIndex[1]; lessDist[1] = distance; - lessIndex[1] = at; + lessIndex[1] = path.number; } else if (distance < lessDist[2]) { lessDist[2] = distance; - lessIndex[2] = at; + lessIndex[2] = path.number; } } int selected = kInvalidNodeIndex; @@ -1894,8 +1891,8 @@ bool Bot::findNextBestNode () { selected = lessIndex[index]; // if we're still have no node and have busy one (by other bot) pick it up - if (selected == kInvalidNodeIndex && busy != kInvalidNodeIndex) { - selected = busy; + if (selected == kInvalidNodeIndex && busyIndex != kInvalidNodeIndex) { + selected = busyIndex; } // worst case... find atleast something @@ -1909,26 +1906,27 @@ bool Bot::findNextBestNode () { } float Bot::getEstimatedNodeReachTime () { - float estimatedTime = 2.8f; + float estimatedTime = 6.0f; + + // if just fired at enemy, increase reachability + if (m_shootTime + 0.15f < game.time ()) { + 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])) { - float distance = graph[m_previousNodes[0]].origin.distance (graph[m_currentNodeIndex].origin); + float distance = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin); // caclulate estimated time - if (pev->maxspeed <= 0.0f) { - estimatedTime = 3.0f * distance / 240.0f; - } - else { - estimatedTime = 3.0f * distance / pev->maxspeed; - } + estimatedTime = 5.0f * (distance / cr::square (m_moveSpeed + 1.0f)); + bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) || (m_pathFlags & NodeFlag::Ladder) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK); // check for special nodes, that can slowdown our movement if (longTermReachability) { estimatedTime *= 2.0f; } - estimatedTime = cr::clamp (estimatedTime, 2.0f, longTermReachability ? 8.0f : 5.0f); + estimatedTime = cr::clamp (estimatedTime, 3.0f, longTermReachability ? 8.0f : 6.0f); } return estimatedTime + m_frameInterval; } @@ -1941,8 +1939,7 @@ void Bot::findValidNode () { clearSearchNodes (); findNextBestNode (); } - else if (m_navTimeset + getEstimatedNodeReachTime () < game.time () && game.isNullEntity (m_enemy)) { - + else if (m_navTimeset + getEstimatedNodeReachTime () < game.time ()) { // increase danager for both teams for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { int damageValue = graph.getDangerDamage (team, m_currentNodeIndex, m_currentNodeIndex); @@ -1989,42 +1986,31 @@ int Bot::findNearestNode () { // get the current nearest node to bot with visibility checks int index = kInvalidNodeIndex; - float minimum = cr::square (1024.0f); + float minimumDistance = cr::square (1024.0f); - auto &bucket = graph.getNodesInBucket (pev->origin); + const auto &origin = pev->origin + pev->velocity * m_frameInterval; + const auto &bucket = graph.getNodesInBucket (origin); - for (const auto at : bucket) { - if (at == m_currentNodeIndex) { + for (const auto &i : bucket) { + const auto &path = graph[i]; + + if (!graph.exists (path.number) || !graph.exists (m_currentNodeIndex) || path.number == m_currentNodeIndex) { continue; } - float distance = graph[at].origin.distanceSq (pev->origin); + const float distance = path.origin.distanceSq (pev->origin); - if (distance < minimum) { - - // if bot doing navigation, make sure node really visible and not too high - if ((m_currentNodeIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, at) && graph.isVisible (at, m_currentNodeIndex)) || isReachableNode (at)) { - index = at; - minimum = distance; + if (distance < minimumDistance) { + // if bot doing navigation, make sure node really visible and reacheable + if (graph.isVisible (m_currentNodeIndex, path.number) && isReachableNode (path.number)) { + index = path.number; + minimumDistance = distance; } - - // @pr-419: temporarily disabled due to cpu usage -#if 0 - else { - TraceResult tr {}; - game.testHull (getEyesPos (), graph[at].origin, TraceIgnore::Monsters, head_hull, ent (), &tr); - - if (tr.flFraction >= 1.0f && !tr.fStartSolid) { - index = at; - minimum = distance; - } - } -#endif } } // worst case, take any node... if (index == kInvalidNodeIndex) { - index = graph.getNearestNoBuckets (pev->origin); + index = graph.getNearestNoBuckets (origin); } return index; } @@ -2109,14 +2095,14 @@ int Bot::findDefendNode (const Vector &origin) { } // find the best node now - for (int i = 0; i < graph.length (); ++i) { + for (const auto &path : graph) { // exclude ladder & current nodes - if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || !graph.isVisible (i, posIndex)) { + if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || !graph.isVisible (path.number, posIndex)) { continue; } // use the 'real' pathfinding distances - int distance = graph.getPathDist (srcIndex, i); + int distance = graph.getPathDist (srcIndex, path.number); // skip wayponts too far if (distance > kMaxDistance) { @@ -2124,10 +2110,10 @@ int Bot::findDefendNode (const Vector &origin) { } // skip occupied points - if (isOccupiedNode (i)) { + if (isOccupiedNode (path.number)) { continue; } - game.testLine (graph[i].origin, graph[posIndex].origin, TraceIgnore::Glass, ent (), &tr); + game.testLine (path.origin, graph[posIndex].origin, TraceIgnore::Glass, ent (), &tr); // check if line not hit anything if (!cr::fequal (tr.flFraction, 1.0f)) { @@ -2135,35 +2121,35 @@ int Bot::findDefendNode (const Vector &origin) { } if (distance > minDistance[0]) { - nodeIndex[0] = i; + nodeIndex[0] = path.number; minDistance[0] = distance; } else if (distance > minDistance[1]) { - nodeIndex[1] = i; + nodeIndex[1] = path.number; minDistance[1] = distance; } else if (distance > minDistance[2]) { - nodeIndex[2] = i; + nodeIndex[2] = path.number; minDistance[2] = distance; } else if (distance > minDistance[3]) { - nodeIndex[3] = i; + nodeIndex[3] = path.number; minDistance[3] = distance; } else if (distance > minDistance[4]) { - nodeIndex[4] = i; + nodeIndex[4] = path.number; minDistance[4] = distance; } else if (distance > minDistance[5]) { - nodeIndex[5] = i; + nodeIndex[5] = path.number; minDistance[5] = distance; } else if (distance > minDistance[6]) { - nodeIndex[6] = i; + nodeIndex[6] = path.number; minDistance[6] = distance; } else if (distance > minDistance[7]) { - nodeIndex[7] = i; + nodeIndex[7] = path.number; minDistance[7] = distance; } } @@ -2198,9 +2184,9 @@ int Bot::findDefendNode (const Vector &origin) { if (nodeIndex[0] == kInvalidNodeIndex) { IntArray found; - for (int i = 0; i < graph.length (); ++i) { - if (origin.distanceSq (graph[i].origin) < cr::square (static_cast (kMaxDistance)) && !graph.isVisible (i, posIndex) && !isOccupiedNode (i)) { - found.push (i); + for (const auto &path : graph) { + if (origin.distanceSq (path.origin) < cr::square (static_cast (kMaxDistance)) && graph.isVisible (path.number, posIndex) && !isOccupiedNode (path.number)) { + found.push (path.number); } } @@ -2261,15 +2247,15 @@ int Bot::findCoverNode (float maxDistance) { changeNodeIndex (srcIndex); // find the best node now - for (int i = 0; i < graph.length (); ++i) { + for (const auto &path : graph) { // exclude ladder, current node and nodes seen by the enemy - if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || graph.isVisible (enemyIndex, i)) { + if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || graph.isVisible (enemyIndex, path.number)) { continue; } bool neighbourVisible = false; // now check neighbour nodes for visibility for (auto &enemy : enemies) { - if (graph.isVisible (enemy, i)) { + if (graph.isVisible (enemy, path.number)) { neighbourVisible = true; break; } @@ -2281,43 +2267,43 @@ int Bot::findCoverNode (float maxDistance) { } // use the 'real' pathfinding distances - int distance = graph.getPathDist (srcIndex, i); - int enemyDistance = graph.getPathDist (enemyIndex, i); + int distance = graph.getPathDist (srcIndex, path.number); + int enemyDistance = graph.getPathDist (enemyIndex, path.number); if (distance >= enemyDistance) { continue; } if (distance < minDistance[0]) { - nodeIndex[0] = i; + nodeIndex[0] = path.number; minDistance[0] = distance; } else if (distance < minDistance[1]) { - nodeIndex[1] = i; + nodeIndex[1] = path.number; minDistance[1] = distance; } else if (distance < minDistance[2]) { - nodeIndex[2] = i; + nodeIndex[2] = path.number; minDistance[2] = distance; } else if (distance < minDistance[3]) { - nodeIndex[3] = i; + nodeIndex[3] = path.number; minDistance[3] = distance; } else if (distance < minDistance[4]) { - nodeIndex[4] = i; + nodeIndex[4] = path.number; minDistance[4] = distance; } else if (distance < minDistance[5]) { - nodeIndex[5] = i; + nodeIndex[5] = path.number; minDistance[5] = distance; } else if (distance < minDistance[6]) { - nodeIndex[6] = i; + nodeIndex[6] = path.number; minDistance[6] = distance; } else if (distance < minDistance[7]) { - nodeIndex[7] = i; + nodeIndex[7] = path.number; minDistance[7] = distance; } } @@ -2326,7 +2312,7 @@ int Bot::findCoverNode (float maxDistance) { for (int i = 0; i < kMaxNodeLinks; ++i) { if (nodeIndex[i] != kInvalidNodeIndex) { int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]); - practice = (practice * 100) / kMaxPracticeDamageValue; + practice = (practice * 100) / graph.getHighestDamageForTeam (m_team); minDistance[i] = (practice * 100) / 8192; minDistance[i] += practice; @@ -2380,15 +2366,16 @@ bool Bot::selectBestNextNode () { } for (auto &link : m_path->links) { - if (link.index != kInvalidNodeIndex && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) { + if (graph.exists (link.index) && m_pathWalk.first () != link.index && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) { // don't use ladder nodes as alternative - if (graph[link.index].flags & NodeFlag::Ladder) { + if (graph[link.index].flags & (NodeFlag::Ladder | PathFlag::Jump)) { continue; } if (graph[link.index].origin.z <= graph[m_currentNodeIndex].origin.z + 10.0f && !isOccupiedNode (link.index)) { m_pathWalk.first () = link.index; + return true; } } @@ -2520,6 +2507,9 @@ bool Bot::advanceMovement () { // is there a jump node right ahead and do we need to draw out the light weapon ? if (willJump && !usesKnife () && m_currentWeapon != Weapon::Scout && !m_isReloading && !usesPistol () && (m_jumpDistance > 175.0f || (dst.z - 32.0f > src.z && m_jumpDistance > 135.0f)) && !(m_states & Sense::SeeingEnemy)) { selectWeaponById (Weapon::Knife); // draw out the knife if we needed + + // mark as switched + m_switchedToKnifeDuringJump = true; } // bot not already on ladder but will be soon? @@ -3100,16 +3090,13 @@ int Bot::getRandomCampDir () { float distTab[kMaxNodesToSearch] {}; uint16_t visibility[kMaxNodesToSearch] {}; - int currentNode = m_currentNodeIndex; - - for (int i = 0; i < graph.length (); ++i) { - if (currentNode == i || !graph.isVisible (currentNode, i)) { + for (const auto &path : graph) { + if (m_currentNodeIndex == path.number || !graph.isVisible (m_currentNodeIndex, path.number)) { continue; } - const auto &path = graph[i]; if (count < kMaxNodesToSearch) { - indices[count] = i; + indices[count] = path.number; distTab[count] = pev->origin.distanceSq (path.origin); visibility[count] = path.vis.crouch + path.vis.stand; @@ -3122,7 +3109,7 @@ int Bot::getRandomCampDir () { for (int j = 0; j < kMaxNodesToSearch; ++j) { if (visBits >= visibility[j] && distance > distTab[j]) { - indices[j] = i; + indices[j] = path.number; distTab[j] = distance; visibility[j] = visBits; @@ -3179,12 +3166,12 @@ void Bot::updateLookAngles () { accelerate += 600.0f; } stiffness += 100.0f; - damping += 5.0f; + damping -= 5.0f; } m_idealAngles = pev->v_angle; - float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x); - float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); + const float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x); + const float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) { m_lookYawVel = 0.0f; @@ -3332,11 +3319,6 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) { continue; } - // just in case, if something happend, and we're not updated yet - if (!util.isAlive (client.ent)) { - continue; - } - // do not check clients far away from us if (pev->origin.distanceSq (client.origin) > cr::square (320.0f)) { continue; @@ -3347,7 +3329,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) { } auto length = client.origin.distanceSq (graph[index].origin); - if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (80.0f), cr::square (140.0f))) { + if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (40.0f), cr::square (90.0f))) { return true; } auto bot = bots[client.ent]; @@ -3355,7 +3337,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) { if (bot == nullptr || bot == this || !bot->m_notKilled) { continue; } - return index == bot->m_currentNodeIndex || bot->getTask ()->data == index; + return bot->m_currentNodeIndex == index; } return false; }