diff --git a/CMakeLists.txt b/CMakeLists.txt index 17894ea..271e35f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ if(GIT_FOUND) target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_GENERATED) endif() -if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)" AND NOT CMAKE_LIBRARY_ARCHITECTURE MATCHES "^(arm|aarch64|ppc)") +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86)|(x86_64)|(X86)|(amd64)|(AMD64)" AND NOT CMAKE_LIBRARY_ARCHITECTURE MATCHES "^(arm|aarch64|ppc)") set(BUILD_X86 true) endif() diff --git a/inc/constant.h b/inc/constant.h index 4a25950..167a3a3 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -439,6 +439,7 @@ constexpr auto kSprayDistanceX2 = kSprayDistance * 2; constexpr auto kMaxChatterRepeatInterval = 99.0f; constexpr auto kViewFrameUpdate = 1.0f / 25.0f; constexpr auto kGrenadeDamageRadius = 385.0f; +constexpr auto kMinMovedDistance = 3.0f; constexpr auto kInfiniteDistanceLong = static_cast (kInfiniteDistance); constexpr auto kMaxWeapons = 32; diff --git a/inc/graph.h b/inc/graph.h index c03f6d2..fbba7d4 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -269,10 +269,6 @@ public: const IntArray &getNodesInBucket (const Vector &pos); public: - int32_t getMaxRouteLength () const { - return (length () / 2) + (kMaxNodes / 256); - } - StringRef getAuthor () const { return m_graphAuthor; } diff --git a/inc/planner.h b/inc/planner.h index 2c1ec1e..1150b2d 100644 --- a/inc/planner.h +++ b/inc/planner.h @@ -151,7 +151,7 @@ public: // get route max length, route length should not be larger than half of map nodes size_t getMaxLength () const { - return m_length / 2; + return m_length / 2 + kMaxNodes / 256; } public: diff --git a/inc/yapb.h b/inc/yapb.h index f99c73f..1acc54a 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -315,6 +315,7 @@ private: PathWalk m_pathWalk {}; // pointer to current node from path Dodge m_dodgeStrafeDir {}; // direction to strafe + Dodge m_avoidAction {}; // player avoid action Fight m_fightStyle {}; // combat style to use CollisionState m_collisionState {}; // collision State FindPath m_pathType {}; // which pathfinder to use diff --git a/src/botlib.cpp b/src/botlib.cpp index e022db3..17f2d88 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -3149,7 +3149,7 @@ void Bot::checkSpawnConditions () { // switch to knife if time to do this if (m_checkKnifeSwitch && m_buyingFinished && m_spawnTime + rg (5.0f, 7.5f) < game.time ()) { - if (rg (1, 100) < 2 && cv_spraypaints) { + if (!game.is (GameFlags::Xash3D) && rg (1, 100) < 2 && cv_spraypaints) { startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.time () + 1.0f, false); } @@ -3204,7 +3204,7 @@ void Bot::checkSpawnConditions () { void Bot::logic () { // this function gets called each frame and is the core of all bot ai. from here all other subroutines are called - float movedDistance = 2.0f; // length of different vector (distance bot moved) + float movedDistance = kMinMovedDistance; // length of different vector (distance bot moved) resetMovement (); diff --git a/src/engine.cpp b/src/engine.cpp index 0c6c39e..064f48d 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -697,6 +697,27 @@ void Game::checkCvarsBounds () { // notify about that ctrl.msg ("Bogus value for cvar '%s', min is '%.1f' and max is '%.1f', and we're got '%s', value reverted to default '%.1f'.", var.name, var.min, var.max, str, var.initial); + continue; + } + + /// prevent min/max problems + if (var.name.contains ("_max")) { + String minVar = String (var.name); + minVar.replace ("_max", "_min"); + + for (auto &mv : m_cvars) { + if (mv.name == minVar) { + const auto minValue = mv.self->as (); + + if (minValue > value) { + var.self->set (minValue); + mv.self->set (value); + + // notify about that + ctrl.msg ("Bogus value for min/max cvar '%s' can't be higher than '%s'. Values swapped.", mv.name, var.name); + } + } + } } } diff --git a/src/graph.cpp b/src/graph.cpp index 16d9d7f..d4c3008 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -2541,7 +2541,7 @@ bool BotGraph::checkNodes (bool teleportPlayer, bool onlyPaths) { } // perform DFS instead of floyd-warshall, this shit speedup this process in a bit - const auto length = cr::min (static_cast (kMaxNodes), m_paths.length ()); + const auto length = cr::min (static_cast (kMaxNodes), m_paths.length () + 1); // ensure valid capacity assert (length > 8 && length < static_cast (kMaxNodes)); diff --git a/src/manager.cpp b/src/manager.cpp index 977747c..321e3f3 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1233,12 +1233,13 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) { // just to be sure m_msgQueue.clear (); - // init path walker - m_pathWalk.init (graph.getMaxRouteLength ()); - // init async planner m_planner = cr::makeUnique (graph.length ()); + // init path walker + m_pathWalk.init (m_planner->getMaxLength ()); + + // init player models parts enumerator m_hitboxEnumerator = cr::makeUnique (); @@ -1291,7 +1292,9 @@ int BotManager::getAliveHumansCount () { int count = 0; for (const auto &client : util.getClients ()) { - if ((client.flags & ClientFlags::Alive) && !bots[client.ent] && !(client.ent->v.flags & FL_FAKECLIENT)) { + if ((client.flags & ClientFlags::Alive) + && !bots[client.ent] + && !(client.ent->v.flags & FL_FAKECLIENT)) { ++count; } } @@ -1299,24 +1302,28 @@ int BotManager::getAliveHumansCount () { } int BotManager::getPlayerPriority (edict_t *ent) { - constexpr auto kHighPriority = 512; + constexpr auto kHighPriority = 1024; // always check for only our own bots auto bot = bots[ent]; // if player just return high prio if (!bot) { - return game.indexOfEntity (ent) + kHighPriority; + return game.indexOfEntity (ent) + kHighPriority * 2; } // give bots some priority - if (bot->m_hasC4 || bot->m_isVIP || bot->m_hasHostage || bot->m_healthValue < ent->v.health || (bot->m_currentTravelFlags & PathFlag::Jump)) { + if (bot->m_hasC4 || bot->m_isVIP || bot->m_hasHostage || (bot->m_currentTravelFlags & PathFlag::Jump)) { return bot->entindex () + kHighPriority; } const auto task = bot->getCurrentTaskId (); - // higher priority if camping or hiding - if (task == Task::Camp || task == Task::Hide || task == Task::MoveToPosition) { + // higher priority if important task + if (task == Task::MoveToPosition + || task == Task::SeekCover + || task == Task::Camp + || task == Task::Hide) { + return bot->entindex () + kHighPriority; } return bot->entindex (); @@ -1623,6 +1630,7 @@ void Bot::newRound () { m_zoomCheckTime = 0.0f; m_strafeSetTime = 0.0f; m_dodgeStrafeDir = Dodge::None; + m_avoidAction = Dodge::None; m_fightStyle = Fight::None; m_lastFightStyleCheck = 0.0f; diff --git a/src/navigate.cpp b/src/navigate.cpp index a0da664..6aa6415 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -486,7 +486,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { const auto otherPrio = bots.getPlayerPriority (client.ent); // give some priorities to bot avoidance - if (ownPrio > otherPrio) { + if (ownPrio < otherPrio) { continue; } @@ -495,7 +495,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { const auto avoidPrio = bots.getPlayerPriority (m_hindrance); // ignore because we're already avoiding someone better - if (avoidPrio > otherPrio) { + if (avoidPrio < otherPrio) { continue; } } @@ -509,8 +509,17 @@ void Bot::doPlayerAvoidance (const Vector &normal) { // found somebody? if (game.isNullEntity (m_hindrance)) { + m_avoidAction = Dodge::None; return; } + else if (m_avoidAction != Dodge::None) { + if (m_avoidAction == Dodge::Left) { + setStrafeSpeed (normal, pev->maxspeed); + } + else if (m_avoidAction == Dodge::Right) { + setStrafeSpeed (normal, -pev->maxspeed); + } + } const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f); // use our movement angles, try to predict where we should be next frame @@ -526,16 +535,25 @@ void Bot::doPlayerAvoidance (const Vector &normal) { const auto nextFrameDistanceSq = pev->origin.distanceSq (m_hindrance->v.origin + m_hindrance->v.velocity * interval); // is player that near now or in future that we need to steer away? - if (movedDistanceSq <= cr::sqrf (64.0f) || (distanceSq <= cr::sqrf (72.0f) && nextFrameDistanceSq < distanceSq)) { + if (movedDistanceSq <= cr::sqrf (64.0f) || (distanceSq <= cr::sqrf (96.0f) && nextFrameDistanceSq < distanceSq)) { const auto &dir = (pev->origin - m_hindrance->v.origin).normalize2d_apx (); // to start strafing, we have to first figure out if the target is on the left side or right side - if ((dir | right.normalize2d_apx ()) > 0.0f) { - setStrafeSpeed (normal, pev->maxspeed); - } - else { - setStrafeSpeed (normal, -pev->maxspeed); + if (m_avoidAction == Dodge::None && m_path->radius > 16.0f && !isInNarrowPlace ()) { + if ((dir | right.normalize2d_apx ()) > 0.0f) { + m_avoidAction = Dodge::Left; + + // start strafing + setStrafeSpeed (normal, pev->maxspeed); + } + else { + m_avoidAction = Dodge::Right; + + // start strafing + setStrafeSpeed (normal, -pev->maxspeed); + } } + m_isStuck = false; if (distanceSq < cr::sqrf (96.0f)) { if ((dir | forward.normalize2d_apx ()) < 0.0f) { @@ -545,6 +563,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) { m_moveSpeed = pev->maxspeed; } } + ignoreCollision (); } } @@ -557,7 +576,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // standing still, no need to check? if (m_lastCollTime < game.time () && getCurrentTaskId () != Task::Attack) { // didn't we move enough previously? - if (movedDistance < 2.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) { + if (movedDistance < kMinMovedDistance && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) { m_prevTime = game.time (); // then consider being stuck m_isStuck = true; @@ -1241,7 +1260,7 @@ bool Bot::updateNavigation () { } float desiredDistanceSq = cr::sqrf (8.0f); - const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin); + float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin); // initialize the radius for a special node type, where the node is considered to be reached if (m_pathFlags & NodeFlag::Lift) { @@ -1264,17 +1283,17 @@ bool Bot::updateNavigation () { else if (m_path->number == cv_debug_goal.as ()) { desiredDistanceSq = 0.0f; } - else if (isOccupiedNode (m_path->number)) { - desiredDistanceSq = cr::sqrf (148.0f); - } else { desiredDistanceSq = cr::max (cr::sqrf (m_path->radius), desiredDistanceSq); } + bool pathHasFlags = false; // check if node has a special travel flags, so they need to be reached more precisely for (const auto &link : m_path->links) { if (link.flags != 0) { desiredDistanceSq = 0.0f; + pathHasFlags = true; + break; } } @@ -1284,6 +1303,12 @@ bool Bot::updateNavigation () { desiredDistanceSq = 0.0f; } + // always increase reachability distance for occupied nodes + if (isOccupiedNode (m_path->number, pathHasFlags)) { + desiredDistanceSq = cr::sqrf (96.0f); + nodeDistanceSq *= 0.5f; + } + // needs precise placement - check if we get past the point if (desiredDistanceSq < cr::sqrf (20.0f) && nodeDistanceSq < cr::sqrf (30.0f)) { const auto predictRangeSq = m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval); @@ -1297,12 +1322,13 @@ bool Bot::updateNavigation () { // is sitting there, so the bot is unable to reach the node because of other player on it, and he starts to jumping and so on // here we're clearing task memory data (important!), since task executor may restart goal with one from memory, so this process // will go in cycle, and forcing bot to re-create new route. - if (m_pathWalk.hasNext () && m_pathWalk.next () == m_pathWalk.last () && isOccupiedNode (m_pathWalk.next (), true)) { + if (m_pathWalk.hasNext () && m_pathWalk.next () == m_pathWalk.last () && isOccupiedNode (m_pathWalk.next (), pathHasFlags)) { getTask ()->data = kInvalidNodeIndex; - m_currentNodeIndex = kInvalidNodeIndex; - clearSearchNodes (); - return false; + m_currentNodeIndex = kInvalidNodeIndex; + m_chosenGoalIndex = kInvalidNodeIndex; + + return true; } if (nodeDistanceSq <= desiredDistanceSq) { @@ -2591,8 +2617,12 @@ void Bot::setPathOrigin () { constexpr int kMaxAlternatives = 5; const float radius = m_path->radius; + auto setNonZeroPathOrigin = [&] () -> void { + m_pathOrigin += Vector { pev->angles.x, cr::wrapAngle (pev->angles.y + rg (-90.0f, 90.0f)), 0.0f }.forward () * rg (0.0f, radius); + }; + // if node radius non zero vary origin a bit depending on the body angles - if (radius > 0.0f) { + if (radius > 16.0f) { int nearestIndex = kInvalidNodeIndex; if (!m_pathWalk.empty () && m_pathWalk.hasNext ()) { @@ -2619,9 +2649,12 @@ void Bot::setPathOrigin () { } if (nearestIndex == kInvalidNodeIndex) { - m_pathOrigin += Vector (pev->angles.x, cr::wrapAngle (pev->angles.y + rg (-90.0f, 90.0f)), 0.0f).forward () * rg (0.0f, radius); + setNonZeroPathOrigin (); } } + else if (radius > 0.0f) { + setNonZeroPathOrigin (); + } if (isOnLadder ()) { TraceResult tr {}; @@ -3246,7 +3279,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) { } const auto distanceSq = client.origin.distanceSq (graph[index].origin); - if (distanceSq < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (90.0f), cr::sqrf (120.0f))) { + if (distanceSq < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (98.0f), cr::sqrf (120.0f))) { return true; } auto bot = bots[client.ent]; diff --git a/src/support.cpp b/src/support.cpp index ffc0cdc..f85b136 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -106,16 +106,19 @@ bool BotSupport::isVisible (const Vector &origin, edict_t *ent) { void BotSupport::decalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex) { // this function draw spraypaint depending on the tracing results. + if (cr::fequal (trace->flFraction, 1.0f)) { + return; + } auto logo = conf.getLogoName (logotypeIndex); int entityIndex = -1, message = TE_DECAL; int decalIndex = engfuncs.pfnDecalIndex (logo.chars ()); - if (decalIndex < 0) { + if (decalIndex <= 0) { decalIndex = engfuncs.pfnDecalIndex ("{lambda06"); } - if (cr::fequal (trace->flFraction, 1.0f)) { + if (decalIndex <= 0) { return; } diff --git a/src/tasks.cpp b/src/tasks.cpp index 4b922a8..1532c94 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -100,6 +100,7 @@ void Bot::normal_ () { if (!(m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && m_seeEnemyTime + 5.0f < game.time () && !m_reloadState && m_timeLogoSpray < game.time () + && !game.is (GameFlags::Xash3D) && cv_spraypaints && rg.chance (50) && m_moveSpeed > getShiftSpeed ()