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:
parent
01b176ad00
commit
3a014e471b
13 changed files with 172 additions and 71 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue