diff --git a/CMakeLists.txt b/CMakeLists.txt index e0f8619..c46a2db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ # SPDX-License-Identifier: MIT # -cmake_minimum_required(VERSION 3.15) project(yapb VERSION 4.5 LANGUAGES CXX) if(NOT ANDROID) @@ -61,19 +60,23 @@ if(GIT_FOUND) target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_GENERATED) endif() +if(NOT WIN32 AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "arm" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^arm64") + set(BUILD_X86 true) +endif() + if((CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND NOT MSVC) target_compile_options(${PROJECT_NAME} PRIVATE -flto=auto -fno-exceptions -fno-rtti -fno-threadsafe-statics -pthread) - if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") target_compile_options(${PROJECT_NAME} PRIVATE -march=armv8-a+fp+simd) - elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "arm" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc") + elseif(BUILD_X86) target_compile_options(${PROJECT_NAME} PRIVATE -mmmx -msse -msse2 -msse3 -mfpmath=sse) endif() if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") target_compile_options(${PROJECT_NAME} PRIVATE -funroll-loops -fomit-frame-pointer -fno-stack-protector -fvisibility=hidden -fvisibility-inlines-hidden -fno-math-errno) - if(NOT WIN32 AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "arm" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc") + if(BUILD_X86) target_compile_options(${PROJECT_NAME} PRIVATE -fdata-sections -ffunction-sections -fcf-protection=none -fno-plt) target_link_options(${PROJECT_NAME} PRIVATE -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/ext/ldscripts/version.lds -Wl,--gc-sections) diff --git a/inc/constant.h b/inc/constant.h index 75c6b26..4a25950 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -434,7 +434,7 @@ namespace TaskPri { constexpr auto kInfiniteDistance = 9999999.0f; constexpr auto kInvalidLightLevel = kInfiniteDistance; constexpr auto kGrenadeCheckTime = 0.6f; -constexpr auto kSprayDistance = 360.0f; +constexpr auto kSprayDistance = 272.0f; constexpr auto kSprayDistanceX2 = kSprayDistance * 2; constexpr auto kMaxChatterRepeatInterval = 99.0f; constexpr auto kViewFrameUpdate = 1.0f / 25.0f; diff --git a/inc/control.h b/inc/control.h index c27da39..8e873d2 100644 --- a/inc/control.h +++ b/inc/control.h @@ -84,6 +84,7 @@ private: bool m_rapidOutput {}; bool m_isMenuFillCommand {}; bool m_ignoreTranslate {}; + bool m_denyCommands {}; int m_menuServerFillTeam {}; int m_interMenuData[4] = { 0, }; @@ -185,6 +186,10 @@ public: m_rapidOutput = force; } + void setDenyCommands (bool deny) { + m_denyCommands = deny; + } + void setIssuer (edict_t *ent) { m_ent = ent; } diff --git a/inc/graph.h b/inc/graph.h index 60e3ef2..c03f6d2 100644 --- a/inc/graph.h +++ b/inc/graph.h @@ -183,6 +183,7 @@ private: IntArray m_sniperPoints {}; IntArray m_rescuePoints {}; IntArray m_visitedGoals {}; + IntArray m_nodeNumbers {}; public: SmallArray m_paths {}; @@ -350,6 +351,11 @@ public: memcpy (&m_graphHeader, hdr, sizeof (StorageHeader)); } + // gets the node numbers + const IntArray &getNodeNumbers () { + return m_nodeNumbers; + } + public: // graph helper for sending message to correct channel template void msg (const char *fmt, Args &&...args); diff --git a/inc/yapb.h b/inc/yapb.h index 867ab48..e02674b 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -760,6 +760,7 @@ public: bool hasShield (); bool isShieldDrawn (); bool findNextBestNode (); + bool findNextBestNodeEx (const IntArray &data, bool returnFailure); bool seesEntity (const Vector &dest, bool fromBody = false); int getAmmo (); diff --git a/src/botlib.cpp b/src/botlib.cpp index c60caa3..6b5afc1 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -161,8 +161,10 @@ void Bot::checkBreakable (edict_t *touch) { m_breakableEntity = lookupBreakable (); } else { - m_breakableEntity = touch; - m_breakableOrigin = game.getEntityOrigin (touch); + if (m_breakableEntity != touch) { + m_breakableEntity = touch; + m_breakableOrigin = game.getEntityOrigin (touch); + } } // re-check from previous steps @@ -177,6 +179,7 @@ void Bot::checkBreakablesAround () { if (!m_buyingFinished || !cv_destroy_breakables_around || usesKnife () + || usesSniper () || rg.chance (25) || !game.hasBreakables () || m_seeEnemyTime + 4.0f > game.time () @@ -208,7 +211,7 @@ void Bot::checkBreakablesAround () { } const auto &origin = game.getEntityOrigin (breakable); - const auto distanceToObstacleSq = origin.distanceSq (pev->origin); + const auto distanceToObstacleSq = origin.distanceSq2d (pev->origin); // too far, skip it if (distanceToObstacleSq > cr::sqrf (radius)) { @@ -221,7 +224,7 @@ void Bot::checkBreakablesAround () { } // maybe time to give up? - if (m_lastBreakable == breakable && m_breakableTime + 1.0f < game.time ()) { + if (m_lastBreakable == breakable && m_breakableTime + 1.5f < game.time ()) { m_ignoredBreakable.emplace (breakable); m_breakableOrigin.clear (); @@ -250,6 +253,12 @@ void Bot::checkBreakablesAround () { edict_t *Bot::lookupBreakable () { // this function checks if bot is blocked by a shoot able breakable in his moving direction + // we're got something already + if (util.isBreakableEntity (m_breakableEntity)) { + return m_breakableEntity; + } + const float detectBreakableDistance = (usesKnife () || isOnLadder ()) ? 32.0f : rg (72.0f, 256.0f); + auto doLookup = [&] (const Vector &start, const Vector &end, const float dist) -> edict_t * { TraceResult tr {}; game.testLine (start, start + (end - start).normalize_apx () * dist, TraceIgnore::None, ent (), &tr); @@ -267,56 +276,18 @@ edict_t *Bot::lookupBreakable () { } return nullptr; }; + auto hit = doLookup (pev->origin, m_destOrigin, detectBreakableDistance); - // got recipe from KWo - for (auto i = 0; i < 5; ++i) { - if (i == 1 && game.isNullEntity (m_breakableEntity)) { - continue; - } - Vector end = m_pathOrigin; - Vector start = getEyesPos (); + if (!game.isNullEntity (hit)) { + return hit; + } + hit = doLookup (getEyesPos (), m_destOrigin, detectBreakableDistance); - if (graph.exists (m_currentNodeIndex)) { - end = graph[m_currentNodeIndex].origin; - } - - switch (i) { - case 0: - if (graph.exists (m_previousNodes[0])) { - start = graph[m_previousNodes[0]].origin; - } - else { - continue; - } - break; - - case 1: - start = getEyesPos (); - end = game.getEntityOrigin (m_breakableEntity); - break; - - case 2: - start = pev->origin; - end = m_destOrigin; - break; - - case 3: - start = getEyesPos (); - end = m_destOrigin; - break; - - case 4: - start = getEyesPos (); - break; - } - auto hit = doLookup (start, end, (usesKnife () || isOnLadder ()) ? 32.0f : rg (72.0f, 256.0f)); - - if (!game.isNullEntity (hit)) { - return hit; - } + if (!game.isNullEntity (hit)) { + return hit; } m_breakableEntity = nullptr; - m_breakableOrigin.clear (); + m_breakableOrigin = nullptr; return nullptr; } diff --git a/src/combat.cpp b/src/combat.cpp index 31620bb..64f2658 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -1076,7 +1076,7 @@ bool Bot::checkZoom (float distance) { return zoomChange; } -void Bot::selectWeapons (float distance, int index, int id, int choosen) { +void Bot::selectWeapons (float distance, int, int id, int choosen) { const auto tab = conf.getRawWeapons (); // we want to fire weapon, don't reload now @@ -1150,7 +1150,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { const float timeDelta = game.time () - m_frameInterval; // need to care for burst fire? - if (distance < kSprayDistance || m_blindTime > game.time () || usesKnife ()) { + if ((distance < kSprayDistance && !isRecoilHigh ()) || m_blindTime > game.time () || usesKnife ()) { if (id == Weapon::Knife) { if (distance < 64.0f) { const auto primaryAttackChance = (m_oldButtons & IN_ATTACK2) ? 80 : 40; @@ -1165,7 +1165,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } else { // if automatic weapon press attack - if (tab[choosen].primaryFireHold && getAmmoInClip () > tab[index].minPrimaryAmmo) { + if (tab[choosen].primaryFireHold) { pev->button |= IN_ATTACK; } diff --git a/src/control.cpp b/src/control.cpp index ec1b0e7..bd1f868 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -2207,6 +2207,10 @@ BotControl::BotControl () { } void BotControl::handleEngineCommands () { + if (m_denyCommands) { + return; + } + collectArgs (); setIssuer (game.getLocalEntity ()); @@ -2215,6 +2219,10 @@ void BotControl::handleEngineCommands () { } bool BotControl::handleClientSideCommandsWrapper (edict_t *ent, bool isMenus) { + if (m_denyCommands) { + return false; + } + collectArgs (); setIssuer (ent); diff --git a/src/engine.cpp b/src/engine.cpp index 6fc07ba..0c6c39e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -51,6 +51,9 @@ void Game::precache () { void Game::levelInitialize (edict_t *entities, int max) { // this function precaches needed models and initialize class variables + // enable command handling + ctrl.setDenyCommands (false); + // re-initialize bot's array bots.destroy (); @@ -215,6 +218,10 @@ void Game::levelShutdown () { // suspend any analyzer tasks analyzer.suspend (); + + // disable command handling + ctrl.setDenyCommands (true); + } void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type) { @@ -761,7 +768,7 @@ bool Game::loadCSBinary () { // lookup for x64 binaries first if (plat.x64) { - libs.insert (0, { "mp_amd64", "cs_amd64" }); + libs.insert (0, { "mp_amd64", "mp_arm64", "cs_arm64", "cs_amd64" }); } auto libCheck = [&] (StringRef mod, StringRef dll) { diff --git a/src/graph.cpp b/src/graph.cpp index 6c83d44..16d9d7f 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -1663,6 +1663,7 @@ void BotGraph::populateNodes () { m_rescuePoints.clear (); m_sniperPoints.clear (); m_visitedGoals.clear (); + m_nodeNumbers.clear (); for (const auto &path : m_paths) { if (path.flags & NodeFlag::TerroristOnly) { @@ -1683,6 +1684,7 @@ void BotGraph::populateNodes () { else if (path.flags & NodeFlag::Rescue) { m_rescuePoints.push (path.number); } + m_nodeNumbers.push (path.number); } } @@ -2275,7 +2277,7 @@ void BotGraph::frame () { // draw the radius circle Vector origin = (path.flags & NodeFlag::Crouch) ? path.origin : path.origin - Vector (0.0f, 0.0f, 18.0f); - Color radiusColor { 36, 36, 255 }; + static Color radiusColor { 36, 36, 255 }; // if radius is nonzero, draw a full circle if (path.radius > 0.0f) { @@ -2805,13 +2807,6 @@ BotGraph::BotGraph () { m_facingAtIndex = kInvalidNodeIndex; m_isOnLadder = false; - m_terrorPoints.clear (); - m_ctPoints.clear (); - m_goalPoints.clear (); - m_campPoints.clear (); - m_rescuePoints.clear (); - m_sniperPoints.clear (); - m_editFlags = 0; m_pathDisplayTime = 0.0f; diff --git a/src/navigate.cpp b/src/navigate.cpp index 0d86cca..29cf7cc 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -1751,7 +1751,24 @@ bool Bot::findNextBestNode () { // this function find a node in the near of the bot if bot had lost his path of pathfinder needs // to be restarted over again. - int busyIndex = kInvalidNodeIndex; + const auto &origin = pev->origin + Vector { pev->velocity.x, pev->velocity.y, 0.0f } * m_frameInterval; + const auto &bucket = graph.getNodesInBucket (origin); + + // maximum number of nodes to recheck without buckets + constexpr auto kNearestRecheckThreshold = 1200; + + // try to search in buckets first + if (!findNextBestNodeEx (bucket, graph.length () < kNearestRecheckThreshold ? true : false)) { + + // fallback to nearest search instead + return findNextBestNodeEx (graph.getNodeNumbers (), false); + } + return true; +} + +bool Bot::findNextBestNodeEx (const IntArray &data, bool returnFailure) { + // this function find a node in the near of the bot if bot had lost his path of pathfinder needs + // to be restarted over again. float lessDist[3] {}; int lessIndex[3] {}; @@ -1760,12 +1777,9 @@ bool Bot::findNextBestNode () { lessDist[i] = kInfiniteDistance; lessIndex[i] = kInvalidNodeIndex; } + const auto &numToSkip = returnFailure ? 0 : cr::clamp (rg (0, 2), 0, static_cast (data.length () / 2)); - const auto &origin = pev->origin + pev->velocity * m_frameInterval; - const auto &bucket = graph.getNodesInBucket (origin); - const auto &numToSkip = cr::clamp (rg (0, 2), 0, static_cast (bucket.length () / 2)); - - for (const auto &i : bucket) { + for (const auto &i : data) { const auto &path = graph[i]; if (!graph.exists (path.number)) { @@ -1801,12 +1815,6 @@ bool Bot::findNextBestNode () { continue; } - // check if node is already used by another bot... - if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (path.number)) { - busyIndex = path.number; - continue; - } - // ignore non-reacheable nodes... if (!isReachableNode (path.number)) { continue; @@ -1845,13 +1853,11 @@ bool Bot::findNextBestNode () { } selected = lessIndex[index]; - // if we're still have no node and have busy one (by other bot) pick it up - if (selected == kInvalidNodeIndex && busyIndex != kInvalidNodeIndex) { - selected = busyIndex; - } - // worst case... find at least something if (selected == kInvalidNodeIndex) { + if (returnFailure) { + return false; + } selected = findNearestNode (); } @@ -1977,7 +1983,7 @@ int Bot::findNearestNode () { // try to search ANYTHING that can be reached if (!graph.exists (index)) { nearestDistanceSq = cr::sqrf (kMaxDistance); - const auto &nearestNodes = graph.getNearestInRadius (kMaxDistance, pev->origin); + const auto &nearestNodes = graph.getNodeNumbers (); for (const auto &i : nearestNodes) { const auto &path = graph[i]; diff --git a/src/tasks.cpp b/src/tasks.cpp index d4ecfc2..4b922a8 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -1460,13 +1460,13 @@ void Bot::escapeFromBomb_ () { } void Bot::shootBreakable_ () { - m_aimFlags |= AimFlags::Override; // breakable destroyed? if (!util.isBreakableEntity (m_breakableEntity)) { completeTask (); return; } + m_aimFlags |= AimFlags::Override; pev->button |= m_campButtons; m_checkTerrain = false; @@ -1475,19 +1475,27 @@ void Bot::shootBreakable_ () { m_navTimeset = game.time (); m_lookAtSafe = m_breakableOrigin; - // is bot facing the breakable? - if (util.getConeDeviation (ent (), m_lookAtSafe) >= 0.95f) { - m_aimFlags |= AimFlags::Override; + const float distToObstacle = pev->origin.distanceSq (m_lookAtSafe); + // is bot facing the breakable? + if (util.getConeDeviation (ent (), m_lookAtSafe) >= 0.90f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; m_wantsToFire = true; m_shootTime = game.time (); + // enforce shooting + if (!usesKnife () && !m_isReloading && !(pev->button & IN_RELOAD) && getAmmoInClip () > 0) { + if (!(m_oldButtons & IN_ATTACK)) { + pev->button |= IN_ATTACK; + } + } + + // if with knife with no ammo, recompute breakable distance if (!hasAnyAmmoInClip () && usesKnife () - && pev->origin.distanceSq (m_lookAtSafe) > cr::sqrf (72.0f)) { + && distToObstacle > cr::sqrf (72.0f)) { completeTask (); }