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 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;

View file

@ -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 <DijkstraAlgo> m_dijkstra {};
UniquePtr <FloydWarshallAlgo> 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);

View file

@ -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 <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:
// returns true if bot is using a sniper rifle
bool usesSniper () const {

View file

@ -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 <float> ();
@ -1751,9 +1752,9 @@ void Bot::syncUpdatePredictedIndex () {
}
ScopedUnlock <Mutex> 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;

View file

@ -348,19 +348,20 @@ int BotControl::cmdExec () {
int BotControl::cmdNode () {
enum args { root, alias, cmd, cmd2 };
static Array <StringRef> allowedOnDedicatedServer {
static Array <StringRef> 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 <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.");
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) {
// 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 <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;
}
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;

View file

@ -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 <int> (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 <int> (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);

View file

@ -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 <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);
// 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);
}
}

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_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 &current = 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 &current = 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 &current = 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 <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
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 <float> ();
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 (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);

View file

@ -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

View file

@ -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;

View file

@ -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 <int> ()) {
if (!isNodeValidForPredict (predictNode)
|| pathLength >= cv_max_nodes_for_predict.as <int> ()) {
return false;
}