diff --git a/inc/graph.h b/inc/graph.h index 117f9ef..60e3ef2 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -219,7 +219,7 @@ public: bool isNodeReacheableEx (const Vector &src, const Vector &destination, const float maxHeight); bool isNodeReacheable (const Vector &src, const Vector &destination); bool isNodeReacheableWithJump (const Vector &src, const Vector &destination); - bool checkNodes (bool teleportPlayer); + bool checkNodes (bool teleportPlayer, bool onlyPaths = false); bool isVisited (int index); bool isAnalyzed () const; diff --git a/inc/planner.h b/inc/planner.h index 9d7f889..2c1ec1e 100644 --- a/inc/planner.h +++ b/inc/planner.h @@ -49,7 +49,7 @@ public: }; // bot heuristic functions for astar planner -class Heuristic final { +class PlannerHeuristic final { public: using Func = float (*) (int, int, int); @@ -86,7 +86,7 @@ public: // A* algorithm for bots class AStarAlgo final : public NonCopyable { public: - using HeuristicFn = Heuristic::Func; + using HeuristicFn = PlannerHeuristic::Func; public: struct Route { @@ -246,6 +246,7 @@ private: UniquePtr m_dijkstra {}; UniquePtr m_floyd {}; bool m_memoryLimitHit {}; + bool m_pathsCheckFailed {}; public: PathPlanner (); @@ -269,6 +270,11 @@ public: return m_floyd.get (); } +public: + bool isPathsCheckFailed () const { + return m_pathsCheckFailed; + } + public: // do the pathfinding bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr); diff --git a/inc/yapb.h b/inc/yapb.h index f0038c2..7ac7cbe 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -662,6 +662,7 @@ public: int m_weaponType {}; // current weapon type int m_ammoInClip[kMaxWeapons] {}; // ammo in clip for each weapons int m_ammo[MAX_AMMO_SLOTS] {}; // total ammo amounts + int m_deathCount {}; // number of bot deaths bool m_isVIP {}; // bot is vip? bool m_isAlive {}; // has the player been killed or has he just respawned @@ -819,6 +820,11 @@ public: // execute client command helper template void issueCommand (const char *fmt, Args &&...args); + // checks if valid prediction index + bool isNodeValidForPredict (const int index) const { + return BotGraph::instance ().exists (index) && index != m_currentNodeIndex; + } + private: // returns true if bot is using a sniper rifle bool usesSniper () const { diff --git a/src/botlib.cpp b/src/botlib.cpp index a3379e2..21fbf1b 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -180,8 +180,9 @@ void Bot::checkBreakablesAround () { || !game.hasBreakables () || m_seeEnemyTime + 4.0f > game.time () || !game.isNullEntity (m_enemy) - || !hasPrimaryWeapon ()) { - + || !hasPrimaryWeapon () + || (m_aimFlags & (AimFlags::Danger | AimFlags::PredictPath | AimFlags::Enemy)) + || getCurrentTaskId () != Task::Normal) { return; } const auto radius = cv_object_destroy_radius.as (); @@ -1751,9 +1752,9 @@ void Bot::syncUpdatePredictedIndex () { } ScopedUnlock unlock (m_predictLock); + const auto &botOrigin = pev->origin; const auto &lastEnemyOrigin = m_lastEnemyOrigin; const auto currentNodeIndex = m_currentNodeIndex; - const auto &botOrigin = pev->origin; if (lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { wipePredict (); @@ -1763,7 +1764,7 @@ void Bot::syncUpdatePredictedIndex () { const int destIndex = graph.getNearest (lastEnemyOrigin); int bestIndex = m_currentNodeIndex; - if (destIndex == kInvalidNodeIndex) { + if (!isNodeValidForPredict (destIndex)) { wipePredict (); return; } @@ -1781,16 +1782,17 @@ void Bot::syncUpdatePredictedIndex () { return true; }); - if (bestIndex != currentNodeIndex) { + if (isNodeValidForPredict (bestIndex)) { m_lastPredictIndex = bestIndex; m_lastPredictLength = pathLength; return; } + wipePredict (); } void Bot::updatePredictedIndex () { - if (m_lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { + if (!m_isAlive || m_lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { return; // do not run task if no last enemy } @@ -4039,6 +4041,9 @@ void Bot::updateHearing () { } auto getHeardOriginWithError = [&] () -> Vector { + if (nearestDistanceSq > cr::sqrf (384.0f)) { + return m_hearedEnemy->v.origin; + } auto error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f; auto origin = m_hearedEnemy->v.origin; diff --git a/src/control.cpp b/src/control.cpp index aa664be..ec1b0e7 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -348,19 +348,20 @@ int BotControl::cmdExec () { int BotControl::cmdNode () { enum args { root, alias, cmd, cmd2 }; - static Array allowedOnDedicatedServer { + static Array allowedOnHLDS { "acquire_editor", "upload", "save", "load", "help", "erase", - "fileinfo" + "fileinfo", + "check" }; // check if cmd is allowed on dedicated server - auto isAllowedOnDedicatedServer = [] (StringRef str) -> bool { - for (const auto &test : allowedOnDedicatedServer) { + auto isAllowedOnHLDS = [] (StringRef str) -> bool { + for (const auto &test : allowedOnHLDS) { if (test == str) { return true; } @@ -369,7 +370,7 @@ int BotControl::cmdNode () { }; // graph editor supported only with editor - if (game.isDedicated () && !graph.hasEditor () && !isAllowedOnDedicatedServer (arg (cmd))) { + if (game.isDedicated () && !graph.hasEditor () && !isAllowedOnHLDS (arg (cmd))) { msg ("Unable to use graph edit commands without setting graph editor player. Please use \"graph acquire_editor\" to acquire rights for graph editing."); return BotCommandResult::Handled; } diff --git a/src/graph.cpp b/src/graph.cpp index a701fc0..6c83d44 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -524,6 +524,14 @@ int BotGraph::getEditorNearest (const float maxRange) { int BotGraph::getNearest (const Vector &origin, const float range, int flags) { // find the nearest node to that origin and return the index + // if not alot of nodes on the map, do not bother to use buckets here, we're dont + // get any performance improvement + constexpr auto kMinNodesForBucketsThreshold = 164; + + if (length () < kMinNodesForBucketsThreshold) { + return getNearestNoBuckets (origin, range, flags); + } + if (range > 256.0f && !cr::fequal (range, kInfiniteDistance)) { return getNearestNoBuckets (origin, range, flags); } @@ -1803,6 +1811,13 @@ bool BotGraph::loadGraphData () { msg ("Warning: Graph data is probably not for this map. Please check bots behaviour."); } } + + // notify user about graph problems + if (planner.isPathsCheckFailed ()) { + ctrl.msg ("Warning: Graph data has failed sanity check."); + ctrl.msg ("Warning: Bots will use only shortest-path algo for path finding."); + ctrl.msg ("Warning: This may significantly affect bots behavior on this map."); + } cv_debug_goal.set (kInvalidNodeIndex); // try to do graph collection, and push them to graph database automatically @@ -2411,14 +2426,14 @@ bool BotGraph::isConnected (int index) { return false; } -bool BotGraph::checkNodes (bool teleportPlayer) { - +bool BotGraph::checkNodes (bool teleportPlayer, bool onlyPaths) { auto teleport = [&] (const Path &path) -> void { if (teleportPlayer) { engfuncs.pfnSetOrigin (m_editor, path.origin); setEditFlag (GraphEdit::On | GraphEdit::Noclip); } }; + const bool showErrors = !onlyPaths; int terrPoints = 0; int ctPoints = 0; @@ -2429,14 +2444,18 @@ bool BotGraph::checkNodes (bool teleportPlayer) { int connections = 0; if (path.number != static_cast (m_paths.index (path))) { - msg ("Node %d path differs from index %d.", path.number, m_paths.index (path)); + if (showErrors) { + msg ("Node %d path differs from index %d.", path.number, m_paths.index (path)); + } break; } for (const auto &test : path.links) { if (test.index != kInvalidNodeIndex) { if (test.index > length ()) { - msg ("Node %d connected with invalid node %d.", path.number, test.index); + if (showErrors) { + msg ("Node %d connected with invalid node %d.", path.number, test.index); + } return false; } ++connections; @@ -2446,14 +2465,18 @@ bool BotGraph::checkNodes (bool teleportPlayer) { if (connections == 0) { if (!isConnected (path.number)) { - msg ("Node %d isn't connected with any other node.", path.number); + if (showErrors) { + msg ("Node %d isn't connected with any other node.", path.number); + } return false; } } if (path.flags & NodeFlag::Camp) { if (path.end.empty ()) { - msg ("Node %d camp-endposition not set.", path.number); + if (showErrors) { + msg ("Node %d camp-endposition not set.", path.number); + } return false; } } @@ -2473,13 +2496,17 @@ bool BotGraph::checkNodes (bool teleportPlayer) { for (const auto &test : path.links) { if (test.index != kInvalidNodeIndex) { if (!exists (test.index)) { - msg ("Node %d path index %d out of range.", path.number, test.index); + if (showErrors) { + msg ("Node %d path index %d out of range.", path.number, test.index); + } teleport (path); return false; } else if (test.index == path.number) { - msg ("Node %d path index %d points to itself.", path.number, test.index); + if (showErrors) { + msg ("Node %d path index %d points to itself.", path.number, test.index); + } teleport (path); return false; @@ -2494,17 +2521,21 @@ bool BotGraph::checkNodes (bool teleportPlayer) { return false; } } - if (terrPoints == 0) { - msg ("You didn't set any terrorist important point."); - return false; - } - else if (ctPoints == 0) { - msg ("You didn't set any CT important point."); - return false; - } - else if (goalPoints == 0) { - msg ("You didn't set any goal point."); - return false; + + // only check paths, but not necessity of different nodes + if (!onlyPaths) { + if (terrPoints == 0) { + msg ("You didn't set any terrorist important point."); + return false; + } + else if (ctPoints == 0) { + msg ("You didn't set any CT important point."); + return false; + } + else if (goalPoints == 0) { + msg ("You didn't set any goal point."); + return false; + } } // perform DFS instead of floyd-warshall, this shit speedup this process in a bit @@ -2545,7 +2576,9 @@ bool BotGraph::checkNodes (bool teleportPlayer) { for (const auto &path : m_paths) { if (!visited[path.number]) { - msg ("Path broken from node 0 to node %d.", path.number); + if (showErrors) { + msg ("Path broken from node 0 to node %d.", path.number); + } teleport (path); return false; @@ -2588,7 +2621,9 @@ bool BotGraph::checkNodes (bool teleportPlayer) { for (const auto &path : m_paths) { if (!visited[path.number]) { - msg ("Path broken from node %d to node 0.", path.number); + if (showErrors) { + msg ("Path broken from node %d to node 0.", path.number); + } teleport (path); return false; diff --git a/src/manager.cpp b/src/manager.cpp index a6ddb3e..b0bf6bc 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -906,7 +906,7 @@ void BotManager::setWeaponMode (int selection) { void BotManager::listBots () { // this function list's bots currently playing on the server - ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s\t%-3.5s\t%-3.8s", "index", "name", "personality", "team", "difficulty", "frags", "alive", "timeleft"); + ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s\t%-3.6s\t%-3.5s\t%-3.8s", "index", "name", "personality", "team", "difficulty", "frags", "deaths", "alive", "timeleft"); auto botTeam = [] (edict_t *ent) -> StringRef { const auto team = game.getRealTeam (ent); @@ -930,9 +930,16 @@ void BotManager::listBots () { for (const auto &bot : bots) { auto timelimitStr = cv_rotate_bots ? strings.format ("%-3.0f secs", bot->m_stayTime - game.time ()) : "unlimited"; - ctrl.msg ("[%-2.1d]\t%-22.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%s", - bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", - botTeam (bot->ent ()), bot->m_difficulty, static_cast (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr); + ctrl.msg ("[%-2.1d]\t%-22.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.1d\t%-3.4s\t%s", + bot->index (), + bot->pev->netname.chars (), + bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", + botTeam (bot->ent ()), + bot->m_difficulty, + static_cast (bot->pev->frags), + bot->m_deathCount, + bot->m_isAlive ? "yes" : "no", + timelimitStr); } ctrl.msg ("%d bots", m_bots.length ()); } @@ -1185,6 +1192,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) { m_heavyTimestamp = game.time (); m_slowFrameTimestamp = 0.0f; m_kpdRatio = 0.0f; + m_deathCount = 0; // stuff from jk_botti m_playServerTime = 60.0f * rg (30.0f, 240.0f); diff --git a/src/message.cpp b/src/message.cpp index a038a88..8aab32a 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -353,6 +353,7 @@ void MessageDispatcher::netMsgScoreInfo () { // if we're have bot, set the kd ratio if (bot != nullptr) { bot->m_kpdRatio = bot->pev->frags / cr::max (static_cast (m_args[deaths].long_), 1.0f); + bot->m_deathCount = m_args[deaths].long_; } } diff --git a/src/navigate.cpp b/src/navigate.cpp index 4b3850e..14f3634 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -362,6 +362,11 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { } } } while (sorting); + + // the most worst case + if (goalChoices[0] == kInvalidNodeIndex) { + return m_chosenGoalIndex = graph.random (); + } return m_chosenGoalIndex = goalChoices[0]; // return and store goal } @@ -1508,8 +1513,9 @@ bool Bot::updateLiftHandling () { else { MDLL_Use (button, ent ()); } + const auto prevNode = m_previousNodes[0]; - if (pev->origin.distanceSq2d (graph[m_previousNodes[0]].origin) < cr::sqrf (64.0f)) { + if (graph.exists (prevNode) && pev->origin.distanceSq2d (graph[prevNode].origin) < cr::sqrf (72.0f)) { wait (); } } @@ -1522,10 +1528,13 @@ bool Bot::updateLiftHandling () { || m_liftState == LiftState::WaitingForTeammates || m_liftState == LiftState::WaitingFor) { + const auto prevNode = m_previousNodes[0]; + if (pev->groundentity == m_liftEntity && !cr::fzero (m_liftEntity->v.velocity.z) && isOnFloor () - && ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) || !game.isNullEntity (m_targetEntity))) { + && ((graph.exists (prevNode) && (graph[prevNode].flags & NodeFlag::Lift)) + || !game.isNullEntity (m_targetEntity))) { m_liftState = LiftState::TravelingBy; m_liftUsageTime = game.time () + 14.0f; @@ -3364,35 +3373,45 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) { } clearSearchNodes (); + m_chosenGoalIndex = srcIndex; + m_goalValue = 0.0f; + + // always use shortest-path algorithm when failed sanity checks within load + if (planner.isPathsCheckFailed ()) { + findShortestPath (srcIndex, destIndex); + + return; + } + // get correct calculation for heuristic if (pathType == FindPath::Optimal) { if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) { - m_planner->setH (Heuristic::hfunctionPathDistWithHostage); - m_planner->setG (Heuristic::gfunctionKillsDistCTWithHostage); + m_planner->setH (PlannerHeuristic::hfunctionPathDistWithHostage); + m_planner->setG (PlannerHeuristic::gfunctionKillsDistCTWithHostage); } else { - m_planner->setH (Heuristic::hfunctionPathDist); - m_planner->setG (Heuristic::gfunctionKillsDist); + m_planner->setH (PlannerHeuristic::hfunctionPathDist); + m_planner->setG (PlannerHeuristic::gfunctionKillsDist); } } else if (pathType == FindPath::Safe) { if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) { - m_planner->setH (Heuristic::hfunctionNone); - m_planner->setG (Heuristic::gfunctionKillsCTWithHostage); + m_planner->setH (PlannerHeuristic::hfunctionNone); + m_planner->setG (PlannerHeuristic::gfunctionKillsCTWithHostage); } else { - m_planner->setH (Heuristic::hfunctionNone); - m_planner->setG (Heuristic::gfunctionKills); + m_planner->setH (PlannerHeuristic::hfunctionNone); + m_planner->setG (PlannerHeuristic::gfunctionKills); } } else { if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) { - m_planner->setH (Heuristic::hfunctionPathDistWithHostage); - m_planner->setG (Heuristic::gfunctionPathDistWithHostage); + m_planner->setH (PlannerHeuristic::hfunctionPathDistWithHostage); + m_planner->setG (PlannerHeuristic::gfunctionPathDistWithHostage); } else { - m_planner->setH (Heuristic::hfunctionPathDist); - m_planner->setG (Heuristic::gfunctionPathDist); + m_planner->setH (PlannerHeuristic::hfunctionPathDist); + m_planner->setG (PlannerHeuristic::gfunctionPathDist); } } diff --git a/src/planner.cpp b/src/planner.cpp index 1982400..6be3b43 100644 --- a/src/planner.cpp +++ b/src/planner.cpp @@ -13,7 +13,7 @@ ConVar cv_path_dijkstra_simple_distance ("path_dijkstra_simple_distance", "1", " ConVar cv_path_astar_post_smooth ("path_astar_post_smooth", "0", "Enables post-smoothing for A*. Reduces zig-zags on paths at cost of some CPU cycles."); ConVar cv_path_randomize_on_round_start ("path_randomize_on_round_start", "1", "Randomize pathfinding on each round start."); -float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex) { +float PlannerHeuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex) { if (parentIndex == kInvalidNodeIndex) { return 0.0f; } @@ -32,7 +32,7 @@ float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex return cost; } -float Heuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex) { +float PlannerHeuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex) { const auto ¤t = graph[currentIndex]; if (current.flags & NodeFlag::NoHostage) { @@ -44,7 +44,7 @@ float Heuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, in return gfunctionKillsDist (team, currentIndex, parentIndex); } -float Heuristic::gfunctionKills (int team, int currentIndex, int) { +float PlannerHeuristic::gfunctionKills (int team, int currentIndex, int) { auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, false); const auto ¤t = graph[currentIndex]; @@ -60,7 +60,7 @@ float Heuristic::gfunctionKills (int team, int currentIndex, int) { return cost; } -auto Heuristic::gfunctionKillsCTWithHostage (int team, int currentIndex, int parentIndex) -> float { +auto PlannerHeuristic::gfunctionKillsCTWithHostage (int team, int currentIndex, int parentIndex) -> float { if (parentIndex == kInvalidNodeIndex) { return 0.0f; } @@ -75,7 +75,7 @@ auto Heuristic::gfunctionKillsCTWithHostage (int team, int currentIndex, int par return gfunctionKills (team, currentIndex, parentIndex); } -float Heuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) { +float PlannerHeuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) { if (parentIndex == kInvalidNodeIndex) { return 0.0f; } @@ -97,7 +97,7 @@ float Heuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) { return kInfiniteHeuristic; } -float Heuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parentIndex) { +float PlannerHeuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parentIndex) { const auto ¤t = graph[currentIndex]; if (current.flags & NodeFlag::NoHostage) { @@ -109,7 +109,7 @@ float Heuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parent return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex); } -float Heuristic::hfunctionPathDist (int index, int, int goalIndex) { +float PlannerHeuristic::hfunctionPathDist (int index, int, int goalIndex) { const auto &start = graph[index]; const auto &goal = graph[goalIndex]; @@ -149,14 +149,14 @@ float Heuristic::hfunctionPathDist (int index, int, int goalIndex) { } } -float Heuristic::hfunctionPathDistWithHostage (int index, int, int goalIndex) { +float PlannerHeuristic::hfunctionPathDistWithHostage (int index, int, int goalIndex) { if (graph[index].flags & NodeFlag::NoHostage) { return kInfiniteHeuristic; } return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex); } -float Heuristic::hfunctionNone (int index, int, int goalIndex) { +float PlannerHeuristic::hfunctionNone (int index, int, int goalIndex) { return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex) / (128.0f * 10.0f); } @@ -264,7 +264,7 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder auto rsRandomizer = 1.0f; // randomize path on round start now and then - if (cv_path_randomize_on_round_start && bots.getRoundStartTime () + 4.0f > game.time ()) { + if (cv_path_randomize_on_round_start && bots.getRoundStartTime () + 2.0f > game.time ()) { rsRandomizer = rg (0.5f, static_cast (botTeam) * 2.0f); } @@ -315,7 +315,7 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder // calculate the F value as F = G + H const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex) * rsRandomizer; const float h = m_hcalc (child.index, kInvalidNodeIndex, destIndex); - const float f = cr::floorf (g + h); + const float f = plat.simd ? g + h : cr::ceilf (g + h + 0.5f); if (childRoute->state == RouteState::New || childRoute->f > f) { // put the current child into open list @@ -515,9 +515,17 @@ void PathPlanner::init () { const float limitInMb = cv_path_floyd_memory_limit.as (); const float memoryUse = static_cast (sizeof (FloydWarshallAlgo::Matrix) * cr::sqrf (static_cast (length)) / 1024 / 1024); + // ensure nodes are valid + m_pathsCheckFailed = !graph.checkNodes (false, true); + // if we're have too much memory for floyd matrices, planner will use dijkstra or uniform planner for other than pathfinding needs if (memoryUse > limitInMb) { m_memoryLimitHit = true; + + // we're need floyd tables when graph has failed sanity checks + if (m_pathsCheckFailed) { + m_memoryLimitHit = false; + } } m_dijkstra->init (length); diff --git a/src/support.cpp b/src/support.cpp index e9022ae..c430078 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -32,7 +32,16 @@ BotSupport::BotSupport () { "engine is operative, hello and goodbye", "high amigo, your administration has been great last day", "attention, expect experimental armed hostile presence", - "warning, medical attention required" + "warning, medical attention required", + "high man, at your command", + "check check, test, mike check, talk device is activated", + "hello pal, at your service", + "good, day mister, your administration is now acknowledged", + "attention, anomalous agent activity, detected", + "mister, you are going down", + "all command access granted, over and out", + "buzwarn hostile presence detected nearest to your sector. over and out. doop" + "hostile resistance detected" }; // register weapon aliases diff --git a/src/tasks.cpp b/src/tasks.cpp index f585b42..921cacd 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -565,7 +565,7 @@ void Bot::blind_ () { && util.isPlayer (m_lastEnemy) && !usesSniper ()) { - auto error = kSprayDistance * cr::powf (m_lastEnemyOrigin.distance (pev->origin), 0.5f) / 2048.0f; + auto error = kSprayDistance * m_lastEnemyOrigin.distance (pev->origin) / 2048.0f; auto origin = m_lastEnemyOrigin; origin.x = origin.x + rg (-error, error); @@ -645,14 +645,14 @@ void Bot::camp_ () { auto pathLength = m_lastPredictLength; auto predictNode = m_lastPredictIndex; - if (pathLength > 1 && graph.exists (predictNode)) { + if (pathLength > 1 && isNodeValidForPredict (predictNode)) { m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; } else { pathLength = 0; predictNode = findAimingNode (m_lastEnemyOrigin, pathLength); - if (pathLength > 1 && graph.exists (predictNode)) { + if (pathLength > 1 && isNodeValidForPredict (predictNode)) { m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; } } @@ -1457,7 +1457,6 @@ void Bot::escapeFromBomb_ () { } void Bot::shootBreakable_ () { - m_aimFlags |= AimFlags::Override; // breakable destroyed? if (!util.isShootableBreakable (m_breakableEntity)) { @@ -1473,6 +1472,8 @@ void Bot::shootBreakable_ () { // is bot facing the breakable? if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.95f && util.isVisible (m_breakableOrigin, ent ())) { + m_aimFlags |= AimFlags::Override; + m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; diff --git a/src/vision.cpp b/src/vision.cpp index a0e17c9..5ec1df4 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -494,7 +494,7 @@ void Bot::setAimDirection () { } auto doFailPredict = [this] () -> void { - if (m_timeNextTracking + 0.5f > game.time ()) { + if (m_lastPredictIndex != m_currentNodeIndex && m_timeNextTracking + 0.5f > game.time ()) { return; // do not fail instantly } m_aimFlags &= ~AimFlags::PredictPath; @@ -507,7 +507,9 @@ void Bot::setAimDirection () { auto predictNode = m_lastPredictIndex; auto isPredictedIndexApplicable = [&] () -> bool { - if (!graph.exists (predictNode) || pathLength >= cv_max_nodes_for_predict.as ()) { + if (!isNodeValidForPredict (predictNode) + || pathLength >= cv_max_nodes_for_predict.as ()) { + return false; }