From 1263a1a4395d3e4f7904127c027d79a0076ce643 Mon Sep 17 00:00:00 2001 From: ds Date: Sat, 26 Sep 2020 18:41:53 +0300 Subject: [PATCH] fix: use clamped health value to avoid overflows. crlib: do not shutdown wsa, it's cleaned upon server exit anyway. --- ext/crlib/cr-http.h | 171 +++++++++++++++++++++----------------------- inc/graph.h | 2 +- inc/yapb.h | 1 + src/botlib.cpp | 19 ++--- src/combat.cpp | 4 +- src/control.cpp | 3 - src/engine.cpp | 10 +-- src/graph.cpp | 15 ++-- src/linkage.cpp | 4 +- src/manager.cpp | 1 + src/navigate.cpp | 5 +- 11 files changed, 109 insertions(+), 126 deletions(-) diff --git a/ext/crlib/cr-http.h b/ext/crlib/cr-http.h index 6198bbc..a383ee6 100644 --- a/ext/crlib/cr-http.h +++ b/ext/crlib/cr-http.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #if defined (CR_LINUX) || defined (CR_OSX) # include @@ -110,37 +112,72 @@ CR_DECLARE_SCOPED_ENUM (HttpClientResult, SocketError = -1, ConnectError = -2, HttpOnly = -3, - Undefined = -4, + Undefined = -4, NoLocalFile = -5, LocalFileExists = -6 ) CR_NAMESPACE_BEGIN + +namespace detail { + + // simple http uri omitting query-string and port + struct HttpUri { + String path, protocol, host; + + public: + static HttpUri parse (StringRef uri) { + HttpUri result; + + if (uri.empty ()) { + return result; + } + size_t protocol = uri.find ("://"); + + if (protocol != String::InvalidIndex) { + result.protocol = uri.substr (0, protocol); + + size_t hostIndex = uri.find ("/", protocol + 3); + + if (hostIndex != String::InvalidIndex) { + result.path = uri.substr (hostIndex + 1); + result.host = uri.substr (protocol + 3, hostIndex - protocol - 3); + + return result; + } + } + return result; + } + }; + + struct SocketInit { + public: + static void start () { +#if defined(CR_WINDOWS) + WSADATA wsa; + + if (WSAStartup (MAKEWORD (2, 2), &wsa) != 0) { + logger.error ("Unable to inialize sockets."); + } +#endif + } + }; +} + class Socket final : public DenyCopying { private: int32 socket_; uint32 timeout_; public: - Socket () : socket_ (-1), timeout_ (2) { -#if defined(CR_WINDOWS) - WSADATA wsa; - - if (WSAStartup (MAKEWORD (2, 2), &wsa) != 0) { - logger.error ("Unable to inialize sockets."); - } -#endif - } + Socket () : socket_ (-1), timeout_ (2) + { } ~Socket () { disconnect (); -#if defined (CR_WINDOWS) - WSACleanup (); -#endif } - public: bool connect (StringRef hostname) { addrinfo hints {}, *result = nullptr; @@ -162,7 +199,7 @@ public: return false; } - auto getTimeouts = [&] () -> Twin { + auto getTimeouts = [&]() -> Twin { #if defined (CR_WINDOWS) DWORD tv = timeout_ * 1000; #else @@ -183,7 +220,7 @@ public: if (::connect (socket_, result->ai_addr, static_cast (result->ai_addrlen)) == -1) { disconnect (); freeaddrinfo (result); - + return false; } freeaddrinfo (result); @@ -240,63 +277,34 @@ public: } }; -namespace detail { - - // simple http uri omitting query-string and port - struct HttpUri { - String path, protocol, host; - - public: - static HttpUri parse (StringRef uri) { - HttpUri result; - - if (uri.empty ()) { - return result; - } - size_t protocol = uri.find ("://"); - - if (protocol != String::InvalidIndex) { - result.protocol = uri.substr (0, protocol); - - size_t hostIndex = uri.find ("/", protocol + 3); - - if (hostIndex != String::InvalidIndex) { - result.path = uri.substr (hostIndex + 1); - result.host = uri.substr (protocol + 3, hostIndex - protocol - 3); - - return result; - } - } - return result; - } - }; -} - // simple http client for downloading/uploading files only class HttpClient final : public Singleton { private: enum : int32 { - MaxReceiveErrors = 12 + MaxReceiveErrors = 12, + DefaultSocketTimeout = 32 }; private: - Socket socket_; String userAgent_ = "crlib"; HttpClientResult statusCode_ = HttpClientResult::Undefined; int32 chunkSize_ = 4096; public: - HttpClient () = default; + HttpClient () { + detail::SocketInit::start (); + } + ~HttpClient () = default; private: - HttpClientResult parseResponseHeader (uint8 *buffer) { + HttpClientResult parseResponseHeader (Socket *socket, uint8 *buffer) { bool isFinished = false; int32 pos = 0, symbols = 0, errors = 0; // prase response header while (!isFinished && pos < chunkSize_) { - if (socket_.recv (&buffer[pos], 1) < 1) { + if (socket->recv (&buffer[pos], 1) < 1) { if (++errors > MaxReceiveErrors) { isFinished = true; } @@ -335,24 +343,24 @@ private: public: // simple blocked download - bool downloadFile (StringRef url, StringRef localPath) { - if (File::exists (localPath.chars ())) { + bool downloadFile (StringRef url, StringRef localPath, int32 timeout = DefaultSocketTimeout) { + if (File::exists (localPath)) { statusCode_ = HttpClientResult::LocalFileExists; return false; } auto uri = detail::HttpUri::parse (url); + auto socket = cr::makeUnique (); // no https... if (uri.protocol == "https") { statusCode_ = HttpClientResult::HttpOnly; return false; } + socket->setTimeout (timeout); // unable to connect... - if (!socket_.connect (uri.host)) { + if (!socket->connect (uri.host)) { statusCode_ = HttpClientResult::ConnectError; - socket_.disconnect (); - return false; } @@ -364,17 +372,15 @@ public: request.appendf ("User-Agent: %s\r\n", userAgent_); request.appendf ("Host: %s\r\n\r\n", uri.host); - if (socket_.send (request.chars (), static_cast (request.length ())) < 1) { + if (socket->send (request.chars (), static_cast (request.length ())) < 1) { statusCode_ = HttpClientResult::SocketError; - socket_.disconnect (); return false; } SmallArray buffer (chunkSize_); - statusCode_ = parseResponseHeader (buffer.data ()); + statusCode_ = parseResponseHeader (socket.get (), buffer.data ()); if (statusCode_ != HttpClientResult::Ok) { - socket_.disconnect (); return false; } @@ -383,15 +389,13 @@ public: if (!file) { statusCode_ = HttpClientResult::Undefined; - socket_.disconnect (); - return false; } int32 length = 0; int32 errors = 0; for (;;) { - length = socket_.recv (buffer.data (), chunkSize_); + length = socket->recv (buffer.data (), chunkSize_); if (length > 0) { file.write (buffer.data (), length); @@ -400,32 +404,29 @@ public: break; } } - file.close (); - - socket_.disconnect (); statusCode_ = HttpClientResult::Ok; return true; } - bool uploadFile (StringRef url, StringRef localPath) { - if (!File::exists (localPath.chars ())) { + bool uploadFile (StringRef url, StringRef localPath, const int32 timeout = DefaultSocketTimeout) { + if (!File::exists (localPath)) { statusCode_ = HttpClientResult::NoLocalFile; return false; } auto uri = detail::HttpUri::parse (url); + auto socket = cr::makeUnique (); // no https... if (uri.protocol == "https") { statusCode_ = HttpClientResult::HttpOnly; return false; } + socket->setTimeout (timeout); // unable to connect... - if (!socket_.connect (uri.host)) { + if (!socket->connect (uri.host)) { statusCode_ = HttpClientResult::ConnectError; - socket_.disconnect (); - return false; } @@ -434,8 +435,6 @@ public: if (!file) { statusCode_ = HttpClientResult::Undefined; - socket_.disconnect (); - return false; } String boundaryName = localPath; @@ -444,7 +443,7 @@ public: if (boundarySlash != String::InvalidIndex) { boundaryName = localPath.substr (boundarySlash + 1); } - StringRef boundaryLine = "---crlib_upload_boundary_1337"; + StringRef boundaryLine = strings.format ("---crlib_upload_boundary_%d%d%d%d", rg.int_ (0, 9), rg.int_ (0, 9), rg.int_ (0, 9), rg.int_ (0, 9)); String request, start, end; start.appendf ("--%s\r\n", boundaryLine); @@ -460,17 +459,14 @@ public: request.appendf ("Content-Length: %d\r\n\r\n", file.length () + start.length () + end.length ()); // send the main request - if (socket_.send (request.chars (), static_cast (request.length ())) < 1) { + if (socket->send (request.chars (), static_cast (request.length ())) < 1) { statusCode_ = HttpClientResult::SocketError; - socket_.disconnect (); - return false; } // send boundary start - if (socket_.send (start.chars (), static_cast (start.length ())) < 1) { + if (socket->send (start.chars (), static_cast (start.length ())) < 1) { statusCode_ = HttpClientResult::SocketError; - socket_.disconnect (); return false; } @@ -481,7 +477,7 @@ public: length = static_cast (file.read (buffer.data (), 1, chunkSize_)); if (length > 0) { - socket_.send (buffer.data (), length); + socket->send (buffer.data (), length); } else { break; @@ -489,14 +485,11 @@ public: } // send boundary end - if (socket_.send (end.chars (), static_cast (end.length ())) < 1) { + if (socket->send (end.chars (), static_cast (end.length ())) < 1) { statusCode_ = HttpClientResult::SocketError; - socket_.disconnect (); - return false; } - statusCode_ = parseResponseHeader (buffer.data ()); - socket_.disconnect (); + statusCode_ = parseResponseHeader (socket.get (), buffer.data ()); return statusCode_ == HttpClientResult::Ok; } @@ -513,10 +506,6 @@ public: void setChunkSize (int32 chunkSize) { chunkSize_ = chunkSize; } - - void setTimeout (uint32 timeout) { - socket_.setTimeout (timeout); - } }; // expose global http client diff --git a/inc/graph.h b/inc/graph.h index 929da1d..ca7973c 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -333,7 +333,7 @@ public: template bool raiseLoadingError (bool isGraph, MemFile &file, const char *fmt, Args &&...args); void saveOldFormat (); - void initGraph (); + void reset (); void frame (); void loadPractice (); void loadVisibility (); diff --git a/inc/yapb.h b/inc/yapb.h index 28cf201..c9be0d1 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -976,6 +976,7 @@ public: float m_timeLastFired; // time to last firing float m_difficultyChange; // time when auto-difficulty was last applied to this bot float m_kpdRatio; // kill per death ratio + float m_healthValue; // clamped bot health int m_basePing; // base ping for bot int m_numEnemiesLeft; // number of enemies alive left on map diff --git a/src/botlib.cpp b/src/botlib.cpp index a20d3dc..cb6f357 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -628,7 +628,7 @@ void Bot::updatePickups () { else if (m_isVIP || !rateGroundWeapon (ent)) { allowPickup = false; } - else if (strcmp (model, "medkit.mdl") == 0 && pev->health >= 100.0f) { + else if (strcmp (model, "medkit.mdl") == 0 && m_healthValue >= 100.0f) { allowPickup = false; } else if ((strcmp (model, "kevlar.mdl") == 0 || strcmp (model, "battery.mdl") == 0) && pev->armorvalue >= 100.0f) { @@ -792,7 +792,7 @@ void Bot::updatePickups () { m_itemIgnore = ent; allowPickup = false; - if (!m_defendedBomb && m_difficulty >= Difficulty::Normal && rg.chance (75) && pev->health < 60) { + if (!m_defendedBomb && m_difficulty >= Difficulty::Normal && rg.chance (75) && m_healthValue < 60) { int index = findDefendNode (origin); startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (30.0f, 70.0f), true); // push camp task on to stack @@ -1931,7 +1931,7 @@ void Bot::filterTasks () { // calculate desires to seek cover or hunt if (util.isPlayer (m_lastEnemy) && !m_lastEnemyOrigin.empty () && !m_hasC4) { - float retreatLevel = (100.0f - (pev->health > 50.0f ? 100.0f : pev->health)) * tempFear; // retreat level depends on bot health + float retreatLevel = (100.0f - (m_healthValue > 50.0f ? 100.0f : m_healthValue)) * tempFear; // retreat level depends on bot health if (m_numEnemiesLeft > m_numFriendsLeft / 2 && m_retreatTime < game.time () && m_seeEnemyTime - rg.float_ (2.0f, 4.0f) < game.time ()) { @@ -2958,6 +2958,7 @@ void Bot::update () { m_canChooseAimDirection = true; m_notKilled = util.isAlive (ent ()); m_team = game.getTeam (ent ()); + m_healthValue = cr::clamp (pev->health, 0.0f, 100.0f); if (game.mapIs (MapFlags::Assassination) && !m_isVIP) { m_isVIP = util.isPlayerVIP (ent ()); @@ -5033,7 +5034,7 @@ void Bot::showDebugOverlay () { auto weapon = util.weaponIdToAlias (m_currentWeapon); String debugData; - debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", pev->netname.chars (), pev->health, pev->armorvalue, taskID, tasks[taskID], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality]); + debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", pev->netname.chars (), m_healthValue, pev->armorvalue, taskID, tasks[taskID], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality]); MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, game.getLocalEntity ()) .writeByte (TE_TEXTMESSAGE) @@ -5117,7 +5118,7 @@ void Bot::takeDamage (edict_t *inflictor, int damage, int armor, int bits) { } else { // attacked by an enemy - if (pev->health > 60.0f) { + if (m_healthValue > 60.0f) { m_agressionLevel += 0.1f; if (m_agressionLevel > 1.0f) { @@ -5186,7 +5187,7 @@ void Bot::takeBlind (int alpha) { m_blindSidemoveSpeed = -pev->maxspeed; } - if (pev->health < 85.0f) { + if (m_healthValue < 85.0f) { m_blindMoveSpeed = -pev->maxspeed; } else if (m_personality == Personality::Careful) { @@ -5208,8 +5209,8 @@ void Bot::updatePracticeValue (int damage) { // only rate goal waypoint if bot died because of the damage // FIXME: could be done a lot better, however this cares most about damage done by sniping or really deadly weapons - if (pev->health - damage <= 0) { - graph.setDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (graph.getDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - static_cast (pev->health / 20), -kMaxPracticeGoalValue, kMaxPracticeGoalValue)); + if (m_healthValue - damage <= 0) { + graph.setDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (graph.getDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - static_cast (m_healthValue / 20), -kMaxPracticeGoalValue, kMaxPracticeGoalValue)); } } @@ -5245,7 +5246,7 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) { victimIndex = findNearestNode (); } - if (pev->health > 20.0f) { + if (m_healthValue > 20.0f) { if (victimTeam == Team::Terrorist || victimTeam == Team::CT) { graph.setDangerDamage (victimIndex, victimIndex, victimIndex, cr::clamp (graph.getDangerDamage (victimTeam, victimIndex, victimIndex), 0, kMaxPracticeDamageValue)); } diff --git a/src/combat.cpp b/src/combat.cpp index b69b5c8..ac759dd 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -883,7 +883,7 @@ void Bot::fireWeapons () { } // use knife if near and good difficulty (l33t dude!) - if (cv_stab_close_enemies.bool_ () && m_difficulty >= Difficulty::Hard && pev->health > 80.0f && !game.isNullEntity (enemy) && pev->health >= enemy->v.health && distance < 100.0f && !isOnLadder () && !isGroupOfEnemies (pev->origin)) { + if (cv_stab_close_enemies.bool_ () && m_difficulty >= Difficulty::Hard && m_healthValue > 80.0f && !game.isNullEntity (enemy) && m_healthValue >= enemy->v.health && distance < 100.0f && !isOnLadder () && !isGroupOfEnemies (pev->origin)) { selectWeapons (distance, selectIndex, selectId, choosenWeapon); return; } @@ -1039,7 +1039,7 @@ void Bot::attackMovement () { approach = 29; } else { - approach = static_cast (pev->health * m_agressionLevel); + approach = static_cast (m_healthValue * m_agressionLevel); if (usesSniper () && approach > 49) { approach = 49; diff --git a/src/control.cpp b/src/control.cpp index 94dfafc..f1e2995 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -754,9 +754,6 @@ int BotControl::cmdNodeUpload () { msg ("you may notice the game freezes a bit during upload and issue request creation. Please, be patient."); msg ("\n"); - // six seconds is enough? - http.setTimeout (6); - // try to upload the file if (http.uploadFile (strings.format ("http://%s/graph", product.download), strings.format ("%sgraph/%s.graph", graph.getDataDirectory (false), game.getMapName ()))) { msg ("Graph file was successfully validated and uploaded to the YaPB Graph DB (%s).", product.download); diff --git a/src/engine.cpp b/src/engine.cpp index 60e4c21..fbb0ef2 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -448,7 +448,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) { } // helper to parse single (not multi) command - auto parsePartArgs = [&] (String &args) { + auto parsePartArgs = [& ] (String &args) { args.trim ("\r\n\t\" "); // trim new lines // we're have empty commands? @@ -465,17 +465,17 @@ void Game::prepareBotArgs (edict_t *ent, String str) { // check if we're got a quoted string if (quote < args.length () && args[quote] == '\"') { - m_botArgs.push (cr::move (args.substr (0, space))); // add command - m_botArgs.push (cr::move (args.substr (quote, args.length () - 1).trim ("\""))); // add string with trimmed quotes + m_botArgs.emplace (args.substr (0, space)); // add command + m_botArgs.emplace (args.substr (quote, args.length () - 1).trim ("\"")); // add string with trimmed quotes } else { for (auto &&arg : args.split (" ")) { - m_botArgs.push (cr::move (arg)); + m_botArgs.emplace (arg); } } } else { - m_botArgs.push (cr::move (args)); // move all the part to args + m_botArgs.emplace (args); // move all the part to args } MDLL_ClientCommand (ent); diff --git a/src/graph.cpp b/src/graph.cpp index 5843c2a..ca8b502 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -18,7 +18,7 @@ ConVar cv_graph_fixcamp ("yb_graph_fixcamp", "1", "Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format."); ConVar cv_graph_url ("yb_graph_url", product.download.chars (), "Specifies the URL from bots will be able to download graph in case of missing local one. Set to empty, if no downloads needed.", false, 0.0f, 0.0f); -void BotGraph::initGraph () { +void BotGraph::reset () { // this function initialize the graph structures.. m_loadAttempts = 0; @@ -1515,7 +1515,7 @@ bool BotGraph::convertOldFormat () { if (header.pointNumber == 0 || header.pointNumber > kMaxNodes) { return false; } - initGraph (); + reset (); m_paths.clear (); for (int i = 0; i < header.pointNumber; ++i) { @@ -1578,8 +1578,6 @@ template bool BotGraph::saveStorage (StringRef ext, StringRef name, // no open no fun if (!file) { logger.error ("Unable to open %s file for writing (filename: '%s').", name, filename); - file.close (); - return false; } int32 rawLength = data.template length () * sizeof (U); @@ -1609,11 +1607,8 @@ template bool BotGraph::saveStorage (StringRef ext, StringRef name, } else { logger.error ("Unable to compress %s data (filename: '%s').", name, filename); - file.close (); - return false; } - file.close (); return true; } @@ -1758,7 +1753,7 @@ template bool BotGraph::loadStorage (StringRef ext, StringRef name, if ((hdr.options & StorageOption::Exten) && exten != nullptr) { file.read (exten, sizeof (ExtenHeader)); } - game.print ("Successfully loaded Bots %s data v%d.0 (%d/%.2fMB).", name, hdr.version, m_paths.length (), static_cast (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); + game.print ("Successfully loaded Bots %s data v%d (%d/%.2fMB).", name, hdr.version, m_paths.length (), static_cast (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); file.close (); return true; @@ -1779,7 +1774,7 @@ bool BotGraph::loadGraphData () { bool dataLoaded = loadStorage ("graph", "Graph", StorageOption::Graph, StorageVersion::Graph, m_paths, &exten, &outOptions); if (dataLoaded) { - initGraph (); + reset (); initBuckets (); // add data to buckets @@ -2783,7 +2778,7 @@ void BotGraph::eraseFromDisk () { logger.error ("Unable to open %s", item); } } - initGraph (); // reintialize points + reset (); // reintialize points m_paths.clear (); } diff --git a/src/linkage.cpp b/src/linkage.cpp index dd1ba0d..82b3d3d 100644 --- a/src/linkage.cpp +++ b/src/linkage.cpp @@ -62,7 +62,7 @@ namespace variadic { vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap); va_end (ap); - if (ent && (ent->v.flags & (FL_FAKECLIENT | FL_DORMANT))) { + if (util.isFakeClient (ent)) { auto bot = bots[ent]; if (bot) { @@ -359,7 +359,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) { if (game.is (GameFlags::Xash3D)) { bots.kickEveryone (true, false); } - graph.initGraph (); + graph.reset (); // clear all the bots bots.destroy (); diff --git a/src/manager.cpp b/src/manager.cpp index cc4a3cc..6a3b51b 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1035,6 +1035,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) { m_agressionLevel = m_baseAgressionLevel; m_fearLevel = m_baseFearLevel; m_nextEmotionUpdate = game.time () + 0.5f; + m_healthValue = bot->v.health; // just to be sure m_msgQueue.clear (); diff --git a/src/navigate.cpp b/src/navigate.cpp index d617371..f0469b6 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -820,7 +820,7 @@ bool Bot::updateNavigation () { // add goal values int goalValue = graph.getDangerValue (m_team, m_chosenGoalIndex, m_currentNodeIndex); - int addedValue = static_cast (pev->health * 0.5f + m_goalValue * 0.5f); + int addedValue = static_cast (m_healthValue * 0.5f + m_goalValue * 0.5f); goalValue = cr::clamp (goalValue + addedValue, -kMaxPracticeGoalValue, kMaxPracticeGoalValue); @@ -1416,8 +1416,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath: if (m_routeQue.length () >= kMaxRouteLength - 1) { logger.error ("A* Search for bot \"%s\" has tried to build path with at least %d nodes. Seems to be graph is broken.", pev->netname.chars (), m_routeQue.length ()); - // bail out to shortest path - findShortestPath (srcIndex, destIndex); + kick (); // kick the bot off... return; }