fix: crash with lift handling

fix: some random crashes with some maps
fix: do not look at breakable until totally sure we're can break it
fix: wrong distance calculation in last enemy position randomizer
fix: problems with cs 1.5 won when built with ``nosmid`` switch (still crashes from time to time)
ctrl: add  bots death count to``yb list`` command
graph: disable buckets for small number of nodes graphs
graph: verify graph consistency and select appropriate pathfinding algorithm
misc: added mor vox sentences to welcome a player
This commit is contained in:
jeefo 2024-12-20 01:04:59 +03:00
commit 3a014e471b
No known key found for this signature in database
GPG key ID: D696786B81B667C8
13 changed files with 172 additions and 71 deletions

View file

@ -219,7 +219,7 @@ public:
bool isNodeReacheableEx (const Vector &src, const Vector &destination, const float maxHeight); bool isNodeReacheableEx (const Vector &src, const Vector &destination, const float maxHeight);
bool isNodeReacheable (const Vector &src, const Vector &destination); bool isNodeReacheable (const Vector &src, const Vector &destination);
bool isNodeReacheableWithJump (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 isVisited (int index);
bool isAnalyzed () const; bool isAnalyzed () const;

View file

@ -49,7 +49,7 @@ public:
}; };
// bot heuristic functions for astar planner // bot heuristic functions for astar planner
class Heuristic final { class PlannerHeuristic final {
public: public:
using Func = float (*) (int, int, int); using Func = float (*) (int, int, int);
@ -86,7 +86,7 @@ public:
// A* algorithm for bots // A* algorithm for bots
class AStarAlgo final : public NonCopyable { class AStarAlgo final : public NonCopyable {
public: public:
using HeuristicFn = Heuristic::Func; using HeuristicFn = PlannerHeuristic::Func;
public: public:
struct Route { struct Route {
@ -246,6 +246,7 @@ private:
UniquePtr <DijkstraAlgo> m_dijkstra {}; UniquePtr <DijkstraAlgo> m_dijkstra {};
UniquePtr <FloydWarshallAlgo> m_floyd {}; UniquePtr <FloydWarshallAlgo> m_floyd {};
bool m_memoryLimitHit {}; bool m_memoryLimitHit {};
bool m_pathsCheckFailed {};
public: public:
PathPlanner (); PathPlanner ();
@ -269,6 +270,11 @@ public:
return m_floyd.get (); return m_floyd.get ();
} }
public:
bool isPathsCheckFailed () const {
return m_pathsCheckFailed;
}
public: public:
// do the pathfinding // do the pathfinding
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr); bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr);

View file

@ -662,6 +662,7 @@ public:
int m_weaponType {}; // current weapon type int m_weaponType {}; // current weapon type
int m_ammoInClip[kMaxWeapons] {}; // ammo in clip for each weapons int m_ammoInClip[kMaxWeapons] {}; // ammo in clip for each weapons
int m_ammo[MAX_AMMO_SLOTS] {}; // total ammo amounts int m_ammo[MAX_AMMO_SLOTS] {}; // total ammo amounts
int m_deathCount {}; // number of bot deaths
bool m_isVIP {}; // bot is vip? bool m_isVIP {}; // bot is vip?
bool m_isAlive {}; // has the player been killed or has he just respawned bool m_isAlive {}; // has the player been killed or has he just respawned
@ -819,6 +820,11 @@ public:
// execute client command helper // execute client command helper
template <typename ...Args> void issueCommand (const char *fmt, Args &&...args); template <typename ...Args> 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: private:
// returns true if bot is using a sniper rifle // returns true if bot is using a sniper rifle
bool usesSniper () const { bool usesSniper () const {

View file

@ -180,8 +180,9 @@ void Bot::checkBreakablesAround () {
|| !game.hasBreakables () || !game.hasBreakables ()
|| m_seeEnemyTime + 4.0f > game.time () || m_seeEnemyTime + 4.0f > game.time ()
|| !game.isNullEntity (m_enemy) || !game.isNullEntity (m_enemy)
|| !hasPrimaryWeapon ()) { || !hasPrimaryWeapon ()
|| (m_aimFlags & (AimFlags::Danger | AimFlags::PredictPath | AimFlags::Enemy))
|| getCurrentTaskId () != Task::Normal) {
return; return;
} }
const auto radius = cv_object_destroy_radius.as <float> (); const auto radius = cv_object_destroy_radius.as <float> ();
@ -1751,9 +1752,9 @@ void Bot::syncUpdatePredictedIndex () {
} }
ScopedUnlock <Mutex> unlock (m_predictLock); ScopedUnlock <Mutex> unlock (m_predictLock);
const auto &botOrigin = pev->origin;
const auto &lastEnemyOrigin = m_lastEnemyOrigin; const auto &lastEnemyOrigin = m_lastEnemyOrigin;
const auto currentNodeIndex = m_currentNodeIndex; const auto currentNodeIndex = m_currentNodeIndex;
const auto &botOrigin = pev->origin;
if (lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) { if (lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_lastEnemy)) {
wipePredict (); wipePredict ();
@ -1763,7 +1764,7 @@ void Bot::syncUpdatePredictedIndex () {
const int destIndex = graph.getNearest (lastEnemyOrigin); const int destIndex = graph.getNearest (lastEnemyOrigin);
int bestIndex = m_currentNodeIndex; int bestIndex = m_currentNodeIndex;
if (destIndex == kInvalidNodeIndex) { if (!isNodeValidForPredict (destIndex)) {
wipePredict (); wipePredict ();
return; return;
} }
@ -1781,16 +1782,17 @@ void Bot::syncUpdatePredictedIndex () {
return true; return true;
}); });
if (bestIndex != currentNodeIndex) { if (isNodeValidForPredict (bestIndex)) {
m_lastPredictIndex = bestIndex; m_lastPredictIndex = bestIndex;
m_lastPredictLength = pathLength; m_lastPredictLength = pathLength;
return; return;
} }
wipePredict ();
} }
void Bot::updatePredictedIndex () { 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 return; // do not run task if no last enemy
} }
@ -4039,6 +4041,9 @@ void Bot::updateHearing () {
} }
auto getHeardOriginWithError = [&] () -> Vector { 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 error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f;
auto origin = m_hearedEnemy->v.origin; auto origin = m_hearedEnemy->v.origin;

View file

@ -348,19 +348,20 @@ int BotControl::cmdExec () {
int BotControl::cmdNode () { int BotControl::cmdNode () {
enum args { root, alias, cmd, cmd2 }; enum args { root, alias, cmd, cmd2 };
static Array <StringRef> allowedOnDedicatedServer { static Array <StringRef> allowedOnHLDS {
"acquire_editor", "acquire_editor",
"upload", "upload",
"save", "save",
"load", "load",
"help", "help",
"erase", "erase",
"fileinfo" "fileinfo",
"check"
}; };
// check if cmd is allowed on dedicated server // check if cmd is allowed on dedicated server
auto isAllowedOnDedicatedServer = [] (StringRef str) -> bool { auto isAllowedOnHLDS = [] (StringRef str) -> bool {
for (const auto &test : allowedOnDedicatedServer) { for (const auto &test : allowedOnHLDS) {
if (test == str) { if (test == str) {
return true; return true;
} }
@ -369,7 +370,7 @@ int BotControl::cmdNode () {
}; };
// graph editor supported only with editor // graph editor supported only with editor
if (game.isDedicated () && !graph.hasEditor () && !isAllowedOnDedicatedServer (arg <StringRef> (cmd))) { if (game.isDedicated () && !graph.hasEditor () && !isAllowedOnHLDS (arg <StringRef> (cmd))) {
msg ("Unable to use graph edit commands without setting graph editor player. Please use \"graph acquire_editor\" to acquire rights for graph editing."); 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; return BotCommandResult::Handled;
} }

View file

@ -524,6 +524,14 @@ int BotGraph::getEditorNearest (const float maxRange) {
int BotGraph::getNearest (const Vector &origin, const float range, int flags) { int BotGraph::getNearest (const Vector &origin, const float range, int flags) {
// find the nearest node to that origin and return the index // 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)) { if (range > 256.0f && !cr::fequal (range, kInfiniteDistance)) {
return getNearestNoBuckets (origin, range, flags); 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."); 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); cv_debug_goal.set (kInvalidNodeIndex);
// try to do graph collection, and push them to graph database automatically // try to do graph collection, and push them to graph database automatically
@ -2411,14 +2426,14 @@ bool BotGraph::isConnected (int index) {
return false; return false;
} }
bool BotGraph::checkNodes (bool teleportPlayer) { bool BotGraph::checkNodes (bool teleportPlayer, bool onlyPaths) {
auto teleport = [&] (const Path &path) -> void { auto teleport = [&] (const Path &path) -> void {
if (teleportPlayer) { if (teleportPlayer) {
engfuncs.pfnSetOrigin (m_editor, path.origin); engfuncs.pfnSetOrigin (m_editor, path.origin);
setEditFlag (GraphEdit::On | GraphEdit::Noclip); setEditFlag (GraphEdit::On | GraphEdit::Noclip);
} }
}; };
const bool showErrors = !onlyPaths;
int terrPoints = 0; int terrPoints = 0;
int ctPoints = 0; int ctPoints = 0;
@ -2429,14 +2444,18 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
int connections = 0; int connections = 0;
if (path.number != static_cast <int> (m_paths.index (path))) { if (path.number != static_cast <int> (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; break;
} }
for (const auto &test : path.links) { for (const auto &test : path.links) {
if (test.index != kInvalidNodeIndex) { if (test.index != kInvalidNodeIndex) {
if (test.index > length ()) { 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; return false;
} }
++connections; ++connections;
@ -2446,14 +2465,18 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
if (connections == 0) { if (connections == 0) {
if (!isConnected (path.number)) { 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; return false;
} }
} }
if (path.flags & NodeFlag::Camp) { if (path.flags & NodeFlag::Camp) {
if (path.end.empty ()) { 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; return false;
} }
} }
@ -2473,13 +2496,17 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
for (const auto &test : path.links) { for (const auto &test : path.links) {
if (test.index != kInvalidNodeIndex) { if (test.index != kInvalidNodeIndex) {
if (!exists (test.index)) { 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); teleport (path);
return false; return false;
} }
else if (test.index == path.number) { 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); teleport (path);
return false; return false;
@ -2494,17 +2521,21 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
return false; return false;
} }
} }
if (terrPoints == 0) {
msg ("You didn't set any terrorist important point."); // only check paths, but not necessity of different nodes
return false; if (!onlyPaths) {
} if (terrPoints == 0) {
else if (ctPoints == 0) { msg ("You didn't set any terrorist important point.");
msg ("You didn't set any CT important point."); return false;
return false; }
} else if (ctPoints == 0) {
else if (goalPoints == 0) { msg ("You didn't set any CT important point.");
msg ("You didn't set any goal point."); return false;
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 // 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) { for (const auto &path : m_paths) {
if (!visited[path.number]) { 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); teleport (path);
return false; return false;
@ -2588,7 +2621,9 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
for (const auto &path : m_paths) { for (const auto &path : m_paths) {
if (!visited[path.number]) { 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); teleport (path);
return false; return false;

View file

@ -906,7 +906,7 @@ void BotManager::setWeaponMode (int selection) {
void BotManager::listBots () { void BotManager::listBots () {
// this function list's bots currently playing on the server // 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 { auto botTeam = [] (edict_t *ent) -> StringRef {
const auto team = game.getRealTeam (ent); const auto team = game.getRealTeam (ent);
@ -930,9 +930,16 @@ void BotManager::listBots () {
for (const auto &bot : bots) { for (const auto &bot : bots) {
auto timelimitStr = cv_rotate_bots ? strings.format ("%-3.0f secs", bot->m_stayTime - game.time ()) : "unlimited"; 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", 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", bot->index (),
botTeam (bot->ent ()), bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr); 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 <int> (bot->pev->frags),
bot->m_deathCount,
bot->m_isAlive ? "yes" : "no",
timelimitStr);
} }
ctrl.msg ("%d bots", m_bots.length ()); 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_heavyTimestamp = game.time ();
m_slowFrameTimestamp = 0.0f; m_slowFrameTimestamp = 0.0f;
m_kpdRatio = 0.0f; m_kpdRatio = 0.0f;
m_deathCount = 0;
// stuff from jk_botti // stuff from jk_botti
m_playServerTime = 60.0f * rg (30.0f, 240.0f); m_playServerTime = 60.0f * rg (30.0f, 240.0f);

View file

@ -353,6 +353,7 @@ void MessageDispatcher::netMsgScoreInfo () {
// if we're have bot, set the kd ratio // if we're have bot, set the kd ratio
if (bot != nullptr) { if (bot != nullptr) {
bot->m_kpdRatio = bot->pev->frags / cr::max (static_cast <float> (m_args[deaths].long_), 1.0f); bot->m_kpdRatio = bot->pev->frags / cr::max (static_cast <float> (m_args[deaths].long_), 1.0f);
bot->m_deathCount = m_args[deaths].long_;
} }
} }

View file

@ -362,6 +362,11 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
} }
} }
} while (sorting); } while (sorting);
// the most worst case
if (goalChoices[0] == kInvalidNodeIndex) {
return m_chosenGoalIndex = graph.random ();
}
return m_chosenGoalIndex = goalChoices[0]; // return and store goal return m_chosenGoalIndex = goalChoices[0]; // return and store goal
} }
@ -1508,8 +1513,9 @@ bool Bot::updateLiftHandling () {
else { else {
MDLL_Use (button, ent ()); 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 (); wait ();
} }
} }
@ -1522,10 +1528,13 @@ bool Bot::updateLiftHandling () {
|| m_liftState == LiftState::WaitingForTeammates || m_liftState == LiftState::WaitingForTeammates
|| m_liftState == LiftState::WaitingFor) { || m_liftState == LiftState::WaitingFor) {
const auto prevNode = m_previousNodes[0];
if (pev->groundentity == m_liftEntity if (pev->groundentity == m_liftEntity
&& !cr::fzero (m_liftEntity->v.velocity.z) && !cr::fzero (m_liftEntity->v.velocity.z)
&& isOnFloor () && 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_liftState = LiftState::TravelingBy;
m_liftUsageTime = game.time () + 14.0f; m_liftUsageTime = game.time () + 14.0f;
@ -3364,35 +3373,45 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
} }
clearSearchNodes (); 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 // get correct calculation for heuristic
if (pathType == FindPath::Optimal) { if (pathType == FindPath::Optimal) {
if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) { if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) {
m_planner->setH (Heuristic::hfunctionPathDistWithHostage); m_planner->setH (PlannerHeuristic::hfunctionPathDistWithHostage);
m_planner->setG (Heuristic::gfunctionKillsDistCTWithHostage); m_planner->setG (PlannerHeuristic::gfunctionKillsDistCTWithHostage);
} }
else { else {
m_planner->setH (Heuristic::hfunctionPathDist); m_planner->setH (PlannerHeuristic::hfunctionPathDist);
m_planner->setG (Heuristic::gfunctionKillsDist); m_planner->setG (PlannerHeuristic::gfunctionKillsDist);
} }
} }
else if (pathType == FindPath::Safe) { else if (pathType == FindPath::Safe) {
if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) { if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) {
m_planner->setH (Heuristic::hfunctionNone); m_planner->setH (PlannerHeuristic::hfunctionNone);
m_planner->setG (Heuristic::gfunctionKillsCTWithHostage); m_planner->setG (PlannerHeuristic::gfunctionKillsCTWithHostage);
} }
else { else {
m_planner->setH (Heuristic::hfunctionNone); m_planner->setH (PlannerHeuristic::hfunctionNone);
m_planner->setG (Heuristic::gfunctionKills); m_planner->setG (PlannerHeuristic::gfunctionKills);
} }
} }
else { else {
if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) { if (game.mapIs (MapFlags::HostageRescue) && m_hasHostage) {
m_planner->setH (Heuristic::hfunctionPathDistWithHostage); m_planner->setH (PlannerHeuristic::hfunctionPathDistWithHostage);
m_planner->setG (Heuristic::gfunctionPathDistWithHostage); m_planner->setG (PlannerHeuristic::gfunctionPathDistWithHostage);
} }
else { else {
m_planner->setH (Heuristic::hfunctionPathDist); m_planner->setH (PlannerHeuristic::hfunctionPathDist);
m_planner->setG (Heuristic::gfunctionPathDist); m_planner->setG (PlannerHeuristic::gfunctionPathDist);
} }
} }

View file

@ -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_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."); 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) { if (parentIndex == kInvalidNodeIndex) {
return 0.0f; return 0.0f;
} }
@ -32,7 +32,7 @@ float Heuristic::gfunctionKillsDist (int team, int currentIndex, int parentIndex
return cost; return cost;
} }
float Heuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex) { float PlannerHeuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, int parentIndex) {
const auto &current = graph[currentIndex]; const auto &current = graph[currentIndex];
if (current.flags & NodeFlag::NoHostage) { if (current.flags & NodeFlag::NoHostage) {
@ -44,7 +44,7 @@ float Heuristic::gfunctionKillsDistCTWithHostage (int team, int currentIndex, in
return gfunctionKillsDist (team, currentIndex, parentIndex); 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); auto cost = practice.plannerGetDamage (team, currentIndex, currentIndex, false);
const auto &current = graph[currentIndex]; const auto &current = graph[currentIndex];
@ -60,7 +60,7 @@ float Heuristic::gfunctionKills (int team, int currentIndex, int) {
return cost; 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) { if (parentIndex == kInvalidNodeIndex) {
return 0.0f; return 0.0f;
} }
@ -75,7 +75,7 @@ auto Heuristic::gfunctionKillsCTWithHostage (int team, int currentIndex, int par
return gfunctionKills (team, currentIndex, parentIndex); return gfunctionKills (team, currentIndex, parentIndex);
} }
float Heuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) { float PlannerHeuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) {
if (parentIndex == kInvalidNodeIndex) { if (parentIndex == kInvalidNodeIndex) {
return 0.0f; return 0.0f;
} }
@ -97,7 +97,7 @@ float Heuristic::gfunctionPathDist (int, int currentIndex, int parentIndex) {
return kInfiniteHeuristic; return kInfiniteHeuristic;
} }
float Heuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parentIndex) { float PlannerHeuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parentIndex) {
const auto &current = graph[currentIndex]; const auto &current = graph[currentIndex];
if (current.flags & NodeFlag::NoHostage) { if (current.flags & NodeFlag::NoHostage) {
@ -109,7 +109,7 @@ float Heuristic::gfunctionPathDistWithHostage (int, int currentIndex, int parent
return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex); 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 &start = graph[index];
const auto &goal = graph[goalIndex]; 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) { if (graph[index].flags & NodeFlag::NoHostage) {
return kInfiniteHeuristic; return kInfiniteHeuristic;
} }
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex); 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); 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; auto rsRandomizer = 1.0f;
// randomize path on round start now and then // 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 <float> (botTeam) * 2.0f); rsRandomizer = rg (0.5f, static_cast <float> (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 // calculate the F value as F = G + H
const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex) * rsRandomizer; const float g = curRoute->g + m_gcalc (botTeam, child.index, currentIndex) * rsRandomizer;
const float h = m_hcalc (child.index, kInvalidNodeIndex, destIndex); 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) { if (childRoute->state == RouteState::New || childRoute->f > f) {
// put the current child into open list // put the current child into open list
@ -515,9 +515,17 @@ void PathPlanner::init () {
const float limitInMb = cv_path_floyd_memory_limit.as <float> (); const float limitInMb = cv_path_floyd_memory_limit.as <float> ();
const float memoryUse = static_cast <float> (sizeof (FloydWarshallAlgo::Matrix) * cr::sqrf (static_cast <size_t> (length)) / 1024 / 1024); const float memoryUse = static_cast <float> (sizeof (FloydWarshallAlgo::Matrix) * cr::sqrf (static_cast <size_t> (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 we're have too much memory for floyd matrices, planner will use dijkstra or uniform planner for other than pathfinding needs
if (memoryUse > limitInMb) { if (memoryUse > limitInMb) {
m_memoryLimitHit = true; m_memoryLimitHit = true;
// we're need floyd tables when graph has failed sanity checks
if (m_pathsCheckFailed) {
m_memoryLimitHit = false;
}
} }
m_dijkstra->init (length); m_dijkstra->init (length);

View file

@ -32,7 +32,16 @@ BotSupport::BotSupport () {
"engine is operative, hello and goodbye", "engine is operative, hello and goodbye",
"high amigo, your administration has been great last day", "high amigo, your administration has been great last day",
"attention, expect experimental armed hostile presence", "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 // register weapon aliases

View file

@ -565,7 +565,7 @@ void Bot::blind_ () {
&& util.isPlayer (m_lastEnemy) && util.isPlayer (m_lastEnemy)
&& !usesSniper ()) { && !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; auto origin = m_lastEnemyOrigin;
origin.x = origin.x + rg (-error, error); origin.x = origin.x + rg (-error, error);
@ -645,14 +645,14 @@ void Bot::camp_ () {
auto pathLength = m_lastPredictLength; auto pathLength = m_lastPredictLength;
auto predictNode = m_lastPredictIndex; auto predictNode = m_lastPredictIndex;
if (pathLength > 1 && graph.exists (predictNode)) { if (pathLength > 1 && isNodeValidForPredict (predictNode)) {
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
} }
else { else {
pathLength = 0; pathLength = 0;
predictNode = findAimingNode (m_lastEnemyOrigin, pathLength); predictNode = findAimingNode (m_lastEnemyOrigin, pathLength);
if (pathLength > 1 && graph.exists (predictNode)) { if (pathLength > 1 && isNodeValidForPredict (predictNode)) {
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs; m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
} }
} }
@ -1457,7 +1457,6 @@ void Bot::escapeFromBomb_ () {
} }
void Bot::shootBreakable_ () { void Bot::shootBreakable_ () {
m_aimFlags |= AimFlags::Override;
// breakable destroyed? // breakable destroyed?
if (!util.isShootableBreakable (m_breakableEntity)) { if (!util.isShootableBreakable (m_breakableEntity)) {
@ -1473,6 +1472,8 @@ void Bot::shootBreakable_ () {
// is bot facing the breakable? // is bot facing the breakable?
if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.95f && util.isVisible (m_breakableOrigin, ent ())) { if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.95f && util.isVisible (m_breakableOrigin, ent ())) {
m_aimFlags |= AimFlags::Override;
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;

View file

@ -494,7 +494,7 @@ void Bot::setAimDirection () {
} }
auto doFailPredict = [this] () -> void { 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 return; // do not fail instantly
} }
m_aimFlags &= ~AimFlags::PredictPath; m_aimFlags &= ~AimFlags::PredictPath;
@ -507,7 +507,9 @@ void Bot::setAimDirection () {
auto predictNode = m_lastPredictIndex; auto predictNode = m_lastPredictIndex;
auto isPredictedIndexApplicable = [&] () -> bool { auto isPredictedIndexApplicable = [&] () -> bool {
if (!graph.exists (predictNode) || pathLength >= cv_max_nodes_for_predict.as <int> ()) { if (!isNodeValidForPredict (predictNode)
|| pathLength >= cv_max_nodes_for_predict.as <int> ()) {
return false; return false;
} }