From a49a4000c91a510e320d258ecbd049d9ed08435f Mon Sep 17 00:00:00 2001 From: jeefo Date: Fri, 23 Jun 2023 19:52:46 +0300 Subject: [PATCH] aim: improved ladder handling view direction aim: improved enemy prediction once again nav: bots with hostages will try to take all hostages that are near with him instead of going directly to rescue zone manager: fixed engine errors when removing bots with kickall with instant parameter graph: strip http:// prefix from graph upload url, it should be always http for now bot: improve handling of smoke grenades on ground (restored code from old yapb2 branch) --- ext/crlib | 2 +- inc/constant.h | 9 ++ inc/planner.h | 4 +- inc/support.h | 3 + inc/yapb.h | 9 +- src/botlib.cpp | 35 +++++--- src/control.cpp | 2 +- src/engine.cpp | 6 +- src/graph.cpp | 2 +- src/manager.cpp | 36 ++++++-- src/navigate.cpp | 190 +++++++++++++++++++++++++--------------- src/support.cpp | 14 ++- src/tasks.cpp | 6 +- src/vision.cpp | 66 ++++++++------ vc/yapb.vcxproj | 3 + vc/yapb.vcxproj.filters | 15 ++++ 16 files changed, 269 insertions(+), 133 deletions(-) diff --git a/ext/crlib b/ext/crlib index 67733ef..c4815b7 160000 --- a/ext/crlib +++ b/ext/crlib @@ -1 +1 @@ -Subproject commit 67733ef6ffd51c538692f311e6cfb26affb3e50e +Subproject commit c4815b7445ae0fc509cba511deee0f2c67834704 diff --git a/inc/constant.h b/inc/constant.h index ef351ae..a62c3db 100644 --- a/inc/constant.h +++ b/inc/constant.h @@ -395,6 +395,15 @@ CR_DECLARE_SCOPED_ENUM (Visibility, None = 0 ) +// goal tactic +CR_DECLARE_SCOPED_ENUM (GoalTactic, + Defensive = 0, + Camp, + Offensive, + Goal, + RescueHostage +) + // frustum sides CR_DECLARE_SCOPED_ENUM (FrustumSide, Top = 0, diff --git a/inc/planner.h b/inc/planner.h index d4dcc8a..f8351b6 100644 --- a/inc/planner.h +++ b/inc/planner.h @@ -99,8 +99,8 @@ private: BinaryHeap > m_routeQue {}; Array m_routes {}; - HeuristicFn m_hcalc; - HeuristicFn m_gcalc; + HeuristicFn m_hcalc {}; + HeuristicFn m_gcalc {}; int m_length {}; diff --git a/inc/support.h b/inc/support.h index e3eded0..63e6b37 100644 --- a/inc/support.h +++ b/inc/support.h @@ -54,6 +54,9 @@ public: // check if entity is a vip bool isPlayerVIP (edict_t *ent); + // check if entity is a hostage entity + bool isHostageEntity (edict_t *ent); + // nearest player search helper bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); diff --git a/inc/yapb.h b/inc/yapb.h index b12b066..8cc3eff 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -362,8 +362,9 @@ private: Vector m_throw {}; // origin of node to throw grenades Vector m_enemyOrigin {}; // target origin chosen for shooting Vector m_grenade {}; // calculated vector for grenades - Vector m_entity {}; // origin of entities like buttons etc. - Vector m_lookAtSafe {}; // aiming vector when camping. + Vector m_entity {}; // origin of entities like buttons etc + Vector m_lookAtSafe {}; // aiming vector when camping + Vector m_lookAtPredict {}; // aiming vector when predicting Vector m_desiredVelocity {}; // desired velocity for jump nodes Vector m_breakableOrigin {}; // origin of breakable @@ -403,7 +404,7 @@ private: bool canDuckUnder (const Vector &normal); bool canJumpUp (const Vector &normal); bool doneCanJumpUp (const Vector &normal, const Vector &right); - bool cantMoveForward (const Vector &normal, TraceResult *tr); + bool isBlockedForward (const Vector &normal, TraceResult *tr); bool canStrafeLeft (TraceResult *tr); bool canStrafeRight (TraceResult *tr); bool isBlockedLeft (); @@ -730,7 +731,7 @@ public: void pushChatterMessage (int message); void tryHeadTowardRadioMessage (); void kill (); - void kick (); + void kick (bool silent = false); void resetDoubleJump (); void startDoubleJump (edict_t *ent); void sendBotToOrigin (const Vector &origin); diff --git a/src/botlib.cpp b/src/botlib.cpp index f000a95..89cb32c 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -115,8 +115,8 @@ void Bot::avoidGrenades () { float distanceMoved = pev->origin.distance (pent->v.origin + pent->v.velocity * m_frameInterval); if (distanceMoved < distance && distance < cr::sqrf (500.0f)) { - const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d (); - const auto &rightSide = pev->v_angle.right ().normalize2d (); + const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d_apx (); + const auto &rightSide = pev->v_angle.right ().normalize2d_apx (); if ((dirToPoint | rightSide) > 0.0f) { m_needAvoidGrenade = -1; @@ -129,15 +129,22 @@ void Bot::avoidGrenades () { } } else if ((pent->v.flags & FL_ONGROUND) && model == "smokegrenade.mdl") { - if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov - 7.0f) { - float distance = pent->v.origin.distance (pev->origin); + if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov / 3.0f) { + const auto &entOrigin = game.getEntityOrigin (pent); + const auto &betweenUs = (entOrigin - pev->origin).normalize_apx (); + const auto &betweenNade = (entOrigin - pev->origin).normalize_apx (); + const auto &betweenResult = ((Vector (betweenNade.y, betweenNade.x, 0.0f) * 150.0f + entOrigin) - pev->origin).normalize_apx (); - // shrink bot's viewing distance to smoke grenade's distance - if (m_viewDistance > distance) { - m_viewDistance = distance; + if ((betweenNade | betweenUs) > (betweenNade | betweenResult) && util.isVisible (pent->v.origin, ent ())) { + const float distance = entOrigin.distance (pev->origin); - if (rg.chance (45)) { - pushChatterMessage (Chatter::BehindSmoke); + // shrink bot's viewing distance to smoke grenade's distance + if (m_viewDistance > distance) { + m_viewDistance = distance; + + if (rg.chance (45)) { + pushChatterMessage (Chatter::BehindSmoke); + } } } } @@ -383,7 +390,7 @@ void Bot::updatePickups () { const bool isHostageRescueMap = game.mapIs (MapFlags::HostageRescue); const bool isCSDM = game.is (GameFlags::CSDM); - if (isHostageRescueMap && (classname.startsWith ("hostage_entity") || classname.startsWith ("monster_scientist"))) { + if (isHostageRescueMap && util.isHostageEntity (ent)) { allowPickup = true; pickupType = Pickup::Hostage; } @@ -794,12 +801,16 @@ void Bot::showChatterIcon (bool show, bool disconnect) { int ownIndex = index (); // do not respect timers while disconnecting bot - for (auto &client : util.getClients ()) { if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } + // dormants not receiving messages + if (client.ent->v.flags & FL_DORMANT) { + continue; + } + // do not respect timers while disconnecting bot if (!show && (client.iconFlags[ownIndex] & ClientFlags::Icon) && (disconnect || client.iconTimestamp[ownIndex] < game.time ())) { sendBotVoice (false, client.ent, entindex ()); @@ -2168,7 +2179,7 @@ bool Bot::reactOnEnemy () { bool Bot::lastEnemyShootable () { // don't allow shooting through walls - if (!(m_aimFlags & AimFlags::LastEnemy) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { + if (!(m_aimFlags & (AimFlags::LastEnemy | AimFlags::PredictPath)) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { return false; } return util.getShootingCone (ent (), m_lastEnemyOrigin) >= 0.90f && isPenetrableObstacle (m_lastEnemyOrigin); diff --git a/src/control.cpp b/src/control.cpp index f042b4b..ffa6bec 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -791,7 +791,7 @@ int BotControl::cmdNodeUpload () { msg ("you may notice the game freezes a bit during upload and issue request creation. Please, be patient."); msg ("\n"); - String uploadUrl = cv_graph_url_upload.str (); + String uploadUrl = strings.format ("https://%s", cv_graph_url_upload.str ()); // try to upload the file if (http.uploadFile (uploadUrl, bstor.buildPath (BotFile::Graph))) { diff --git a/src/engine.cpp b/src/engine.cpp index 80bcd99..4fcf2fa 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -124,7 +124,7 @@ void Game::levelInitialize (edict_t *entities, int max) { else if (classname == "func_vip_safetyzone" || classname == "info_vip_safetyzone") { m_mapFlags |= MapFlags::Assassination; // assassination map } - else if (classname == "hostage_entity" || classname == "monster_scientist") { + else if (util.isHostageEntity (ent)) { m_mapFlags |= MapFlags::HostageRescue; // rescue map } else if (classname == "func_bomb_target" || classname == "info_bomb_target") { @@ -916,7 +916,7 @@ bool Game::postload () { if (is (GameFlags::Metamod)) { return true; // we should stop the attempt for loading the real gamedll, since metamod handle this for us } - auto gamedll = strings.format ("%s/%s", plat.env ("XASH3D_GAMELIBDIR"), plat.hfp ? "libserver_hardfp.so" : "libserver.so"); + auto gamedll = strings.format ("%s/%s", plat.env ("XASH3D_GAMELIBDIR"), "libserver.so"); if (!m_gameLib.load (gamedll)) { logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getRunningModName ()); @@ -1137,7 +1137,7 @@ void Game::printBotVersion () { simdLevels.push ("4.2"); } if (cpuflags.neon) { - simdLevels.push ("NEON"); + simdLevels.push ("Neon"); } botRuntimeFlags.push (strings.format ("SIMD: %s", String::join (simdLevels, " & "))); } diff --git a/src/graph.cpp b/src/graph.cpp index 6fd205b..d611f77 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -9,7 +9,7 @@ ConVar cv_graph_fixcamp ("yb_graph_fixcamp", "0", "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 which 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); -ConVar cv_graph_url_upload ("yb_graph_url_upload", "http://yapb.jeefo.net/upload", "Specifies the URL to which bots will try to upload the graph file to database.", false, 0.0f, 0.0f); +ConVar cv_graph_url_upload ("yb_graph_url_upload", "yapb.jeefo.net/upload", "Specifies the URL to which bots will try to upload the graph file to database.", false, 0.0f, 0.0f); ConVar cv_graph_auto_save_count ("yb_graph_auto_save_count", "15", "Every N graph nodes placed on map, the graph will be saved automatically (without checks).", true, 0.0f, kMaxNodes); ConVar cv_graph_draw_distance ("yb_graph_draw_distance", "400", "Maximum distance to draw graph nodes from editor viewport.", true, 64.0f, 3072.0f); diff --git a/src/manager.cpp b/src/manager.cpp index d850e43..ecce217 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -609,7 +609,7 @@ void BotManager::kickEveryone (bool instant, bool zeroQuota) { if (instant) { for (const auto &bot : m_bots) { - bot->kick (); + bot->kick (true); } } m_addRequests.clear (); @@ -618,12 +618,26 @@ void BotManager::kickEveryone (bool instant, bool zeroQuota) { void BotManager::kickFromTeam (Team team, bool removeAll) { // this function remove random bot from specified team (if removeAll value = 1 then removes all players from team) + if (removeAll) { + const auto &counts = countTeamPlayers (); + + m_quotaMaintainTime = game.time () + 3.0f; + m_addRequests.clear (); + + if (team == Team::Terrorist) { + decrementQuota (counts.first); + } + else { + decrementQuota (counts.second); + } + } + for (const auto &bot : m_bots) { if (team == bot->m_team) { - decrementQuota (); - bot->kick (); + bot->kick (removeAll); if (!removeAll) { + decrementQuota (); break; } } @@ -1524,6 +1538,11 @@ void Bot::resetPathSearchType () { m_pathType = morale ? FindPath::Optimal : FindPath::Safe; break; } + + // if debug goal - set the fastest + if (cv_debug_goal.int_ () != kInvalidNodeIndex) { + m_pathType = FindPath::Fast; + } } void Bot::kill () { @@ -1533,7 +1552,7 @@ void Bot::kill () { bots.touchKillerEntity (this); } -void Bot::kick () { +void Bot::kick (bool silent) { // this function kick off one bot from the server. auto username = pev->netname.chars (); @@ -1543,7 +1562,10 @@ void Bot::kick () { markStale (); game.serverCommand ("kick \"%s\"", username); - ctrl.msg ("Bot '%s' kicked.", username); + + if (!silent) { + ctrl.msg ("Bot '%s' kicked.", username); + } } void Bot::markStale () { @@ -1559,7 +1581,7 @@ void Bot::markStale () { // clear fakeclient bit pev->flags &= ~FL_FAKECLIENT; - // make as not receiveing any messages + // make as not receiving any messages pev->flags |= FL_DORMANT; } @@ -1787,7 +1809,7 @@ void BotManager::updateInterestingEntities () { } // pickup some hostage if on cs_ maps - if (game.mapIs (MapFlags::HostageRescue) && classname.startsWith ("hostage")) { + if (game.mapIs (MapFlags::HostageRescue) && util.isHostageEntity (e)) { m_interestingEntities.push (e); } diff --git a/src/navigate.cpp b/src/navigate.cpp index 6a3c320..8b2e517 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -27,18 +27,11 @@ int Bot::findBestGoal () { return result; } } - int tactic = 0; // path finding behavior depending on map type float offensive = 0.0f; float defensive = 0.0f; - float goalDesire = 0.0f; - float forwardDesire = 0.0f; - float campDesire = 0.0f; - float backoffDesire = 0.0f; - float tacticChoice = 0.0f; - IntArray *offensiveNodes = nullptr; IntArray *defensiveNodes = nullptr; @@ -57,12 +50,51 @@ int Bot::findBestGoal () { // terrorist carrying the C4? if (m_hasC4 || m_isVIP) { - tactic = 3; - return findGoalPost (tactic, defensiveNodes, offensiveNodes); + return findGoalPost (GoalTactic::Goal, defensiveNodes, offensiveNodes); } else if (m_team == Team::CT && m_hasHostage) { - tactic = 4; - return findGoalPost (tactic, defensiveNodes, offensiveNodes); + bool hasMoreHostagesAround = false; + + // try to search nearby-unused hostage, and if so, go to next goal + if (bots.hasInterestingEntities ()) { + const auto &interesting = bots.getInterestingEntities (); + + // search world for hostages + for (const auto &ent : interesting) { + if (!util.isHostageEntity (ent)) { + continue; + } + bool hostageInUse = false; + + // do not stole from bots (ignore humans, fuck them) + for (const auto &other : bots) { + if (!other->m_notKilled) { + continue; + } + + for (const auto &hostage : other->m_hostages) { + if (hostage == ent) { + hostageInUse = true; + break; + } + } + } + + // in-use, skip + if (hostageInUse) { + continue; + } + const auto &origin = game.getEntityOrigin (ent); + + // too far, go to rescue point + if (origin.distanceSq2d (pev->origin) > 1024.0f) { + continue; + } + hasMoreHostagesAround = true; + break; + } + } + return findGoalPost (hasMoreHostagesAround ? GoalTactic::Goal : GoalTactic::RescueHostage, defensiveNodes, offensiveNodes); } auto difficulty = static_cast (m_difficulty); @@ -110,39 +142,39 @@ int Bot::findBestGoal () { } else if (game.mapIs (MapFlags::Escape)) { if (m_team == Team::Terrorist) { - offensive += 25.0f; - defensive -= 25.0f; + offensive += 25.0f + difficulty * 4.0f; + defensive -= 25.0f - difficulty * 0.5f; } else if (m_team == Team::CT) { - offensive -= 25.0f; - defensive += 25.0f; + offensive -= 25.0f - difficulty * 4.5f; + defensive += 25.0f + difficulty * 0.5f; } } - goalDesire = rg.get (0.0f, 100.0f) + offensive; - forwardDesire = rg.get (0.0f, 100.0f) + offensive; - campDesire = rg.get (0.0f, 100.0f) + defensive; - backoffDesire = rg.get (0.0f, 100.0f) + defensive; + float goalDesire = rg.get (0.0f, 100.0f) + offensive; + float forwardDesire = rg.get (0.0f, 100.0f) + offensive; + float campDesire = rg.get (0.0f, 100.0f) + defensive; + float backoffDesire = rg.get (0.0f, 100.0f) + defensive; if (!usesCampGun ()) { campDesire *= 0.5f; } - tacticChoice = backoffDesire; - tactic = 0; + int tactic = GoalTactic::Defensive; + float tacticChoice = backoffDesire; if (campDesire > tacticChoice) { tacticChoice = campDesire; - tactic = 1; + tactic = GoalTactic::Camp; } if (forwardDesire > tacticChoice) { tacticChoice = forwardDesire; - tactic = 2; + tactic = GoalTactic::Offensive; } if (goalDesire > tacticChoice) { - tactic = 3; + tactic = GoalTactic::Goal; } return findGoalPost (tactic, defensiveNodes, offensiveNodes); } @@ -209,10 +241,10 @@ int Bot::findBestGoalWhenBombAction () { int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; - if (tactic == 0 && !(*defensive).empty ()) { // careful goal + if (tactic == GoalTactic::Defensive && !(*defensive).empty ()) { // careful goal postprocessGoals (*defensive, goalChoices); } - else if (tactic == 1 && !graph.m_campPoints.empty ()) // camp node goal + else if (tactic == GoalTactic::Camp && !graph.m_campPoints.empty ()) // camp node goal { // pickup sniper points if possible for sniping bots if (!graph.m_sniperPoints.empty () && usesSniper ()) { @@ -222,10 +254,10 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { postprocessGoals (graph.m_campPoints, goalChoices); } } - else if (tactic == 2 && !(*offensive).empty ()) { // offensive goal + else if (tactic == GoalTactic::Offensive && !(*offensive).empty ()) { // offensive goal postprocessGoals (*offensive, goalChoices); } - else if (tactic == 3 && !graph.m_goalPoints.empty ()) // map goal node + else if (tactic == GoalTactic::Goal && !graph.m_goalPoints.empty ()) // map goal node { // force bomber to select closest goal, if round-start goal was reset by something if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ()) { @@ -258,7 +290,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) { postprocessGoals (graph.m_goalPoints, goalChoices); } } - else if (tactic == 4 && !graph.m_rescuePoints.empty ()) { + else if (tactic == GoalTactic::RescueHostage && !graph.m_rescuePoints.empty ()) { // force ct with hostage(s) to select closest rescue goal float minDist = kInfiniteDistance; int count = 0; @@ -316,11 +348,6 @@ void Bot::postprocessGoals (const IntArray &goals, int result[]) { return true; } - // too less to choice from just return all the goals - if (goals.length () < 4) { - return false; - } - // check if historical goal for (const auto &hg : m_goalHist) { if (hg == index) { @@ -336,6 +363,14 @@ void Bot::postprocessGoals (const IntArray &goals, int result[]) { return isOccupiedNode (index); }; + // too less to choice from just return all the goals + if (goals.length () < 4) { + for (size_t i = 0; i < goals.length (); ++i) { + result[i] = goals[i]; + } + return; + } + for (int index = 0; index < 4; ++index) { auto goal = goals.random (); @@ -484,7 +519,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // not stuck yet else { // test if there's something ahead blocking the way - if (!isOnLadder () && cantMoveForward (dirNormal, &tr)) { + if (!isOnLadder () && isBlockedForward (dirNormal, &tr)) { if (cr::fzero (m_firstCollideTime)) { m_firstCollideTime = game.time () + 0.2f; } @@ -528,6 +563,11 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { bits |= (CollisionProbe::Strafe | CollisionProbe::Jump); } + // try to duck when graph analyzed + if (graph.isAnalyzed ()) { + bits |= CollisionProbe::Duck; + } + // collision check allowed if not flying through the air if (isOnFloor () || isOnLadder () || isInWater ()) { uint32_t state[kMaxCollideMoves * 2 + 1] {}; @@ -657,7 +697,6 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } ++i; -#if 0 if (bits & CollisionProbe::Duck) { state[i] = 0; @@ -670,7 +709,6 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } } else -#endif state[i] = 0; ++i; @@ -909,24 +947,31 @@ bool Bot::updateNavigation () { selectBestWeapon (); } } - +#if 0 + if (m_path->flags & NodeFlag::Ladder) { + + } +#else if ((m_pathFlags & NodeFlag::Ladder) || isOnLadder ()) { - if (graph.exists (m_previousNodes[0]) && (graph[m_previousNodes[0]].flags & NodeFlag::Ladder)) { - if (cr::abs (m_pathOrigin.z - pev->origin.z) > 5.0f) { - m_pathOrigin.z += pev->origin.z - m_pathOrigin.z; + constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f); + + if (m_pathOrigin.z >= (pev->origin.z + 16.0f)) { + m_pathOrigin = m_path->origin + kLadderOffset; + } + else if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !(pev->flags & FL_DUCKING)) { + m_moveSpeed = pev->origin.distance (m_pathOrigin); + + if (m_moveSpeed < 150.0f) { + m_moveSpeed = 150.0f; } - if (m_pathOrigin.z > (pev->origin.z + 16.0f)) { - m_pathOrigin = m_pathOrigin - Vector (0.0f, 0.0f, 16.0f); - } - if (m_pathOrigin.z < (pev->origin.z - 16.0f)) { - m_pathOrigin = m_pathOrigin + Vector (0.0f, 0.0f, 16.0f); + else if (m_moveSpeed > pev->maxspeed) { + m_moveSpeed = pev->maxspeed; } } - m_destOrigin = m_pathOrigin; // special detection if someone is using the ladder (to prevent to have bots-towers on ladders) for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || (client.ent->v.movetype != MOVETYPE_FLY) || client.ent == nullptr || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || (client.ent->v.movetype != MOVETYPE_FLY) || client.ent == ent ()) { continue; } TraceResult tr {}; @@ -994,6 +1039,7 @@ bool Bot::updateNavigation () { } } +#endif // special lift handling (code merged from podbotmm) if (m_pathFlags & NodeFlag::Lift) { @@ -1043,30 +1089,29 @@ bool Bot::updateNavigation () { } // if bot hits the door, then it opens, so wait a bit to let it open safely - if (pev->velocity.length2d () < 10 && m_timeDoorOpen < game.time ()) { + if (pev->velocity.lengthSq2d () < cr::sqrf (10.0f) && m_timeDoorOpen < game.time ()) { startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.5f, false); m_timeDoorOpen = game.time () + 1.0f; // retry in 1 sec until door is open - edict_t *pent = nullptr; + ++m_tryOpenDoor; - if (++m_tryOpenDoor > 1 && util.findNearestPlayer (reinterpret_cast (&pent), ent (), 384.0f, false, false, true, true, false)) { - if (isPenetrableObstacle (pent->v.origin)) { + if (m_tryOpenDoor > 2 && util.isAlive (m_lastEnemy)) { + if (isPenetrableObstacle (m_lastEnemy->v.origin) && !cv_ignore_enemies.bool_ ()) { m_seeEnemyTime = game.time (); m_states |= Sense::SeeingEnemy | Sense::SuspectEnemy; m_aimFlags |= AimFlags::Enemy; - m_lastEnemy = pent; - m_enemy = pent; - m_lastEnemyOrigin = pent->v.origin; + m_enemy = m_lastEnemy; + m_lastEnemyOrigin = m_lastEnemy->v.origin; m_tryOpenDoor = 0; } - else { - m_tryOpenDoor = 0; - } } - else if (m_timeDoorOpen + 2.0f < game.time ()) { + else if (m_tryOpenDoor > 4) { + clearSearchNodes (); + clearTasks (); + m_tryOpenDoor = 0; } } @@ -2241,7 +2286,7 @@ bool Bot::advanceMovement () { for (const auto &link : m_path->links) { if (link.index == destIndex) { m_currentTravelFlags = link.flags; - m_desiredVelocity = link.velocity; + m_desiredVelocity = link.velocity - link.velocity * m_frameInterval; m_jumpFinished = false; isCurrentJump = true; @@ -2371,7 +2416,7 @@ void Bot::setPathOrigin () { } } -bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { +bool Bot::isBlockedForward (const Vector &normal, TraceResult *tr) { // checks if bot is blocked in his movement direction (excluding doors) // use some TraceLines to determine if anything is blocking the current path of the bot. @@ -2398,11 +2443,12 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } return true; // bot's head will hit something } + constexpr auto kVec00N16 = Vector (0.0f, 0.0f, -16.0f); // bot's head is clear, check at shoulder level... // trace from the bot's shoulder left diagonal forward to the right shoulder... - src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f; - forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f + normal * 24.0f; + src = getEyesPos () + kVec00N16 - right * -16.0f; + forward = getEyesPos () + kVec00N16 + right * 16.0f + normal * 24.0f; game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2413,8 +2459,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { // bot's head is clear, check at shoulder level... // trace from the bot's shoulder right diagonal forward to the left shoulder... - src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f; - forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f + normal * 24.0f; + src = getEyesPos () + kVec00N16 + right * 16.0f; + forward = getEyesPos () + kVec00N16 - right * -16.0f + normal * 24.0f; game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2445,9 +2491,12 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } } else { + constexpr auto kVec00N17 = Vector (0.0f, 0.0f, -17.0f); + constexpr auto kVec00N24 = Vector (0.0f, 0.0f, -24.0f); + // trace from the left waist to the right forward waist pos - src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - right * -16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f + normal * 24.0f; + src = pev->origin + kVec00N17 - right * -16.0f; + forward = pev->origin + kVec00N17 + right * 16.0f + normal * 24.0f; // trace from the bot's waist straight forward... game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2458,8 +2507,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } // trace from the left waist to the right forward waist pos - src = pev->origin + Vector (0.0f, 0.0f, -24.0f) + right * 16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -24.0f) - right * -16.0f + normal * 24.0f; + src = pev->origin + kVec00N24 + right * 16.0f; + forward = pev->origin + kVec00N24 - right * -16.0f + normal * 24.0f; game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); @@ -2828,7 +2877,7 @@ bool Bot::isDeadlyMove (const Vector &to) { if (tr.fStartSolid) { return false; } - float height = tr.flFraction * 1000.0f; // height from ground + const float height = tr.flFraction * 1000.0f; // height from ground // drops more than 150 units? if (lastHeight < height - 150.0f) { @@ -2862,7 +2911,6 @@ void Bot::changePitch (float speed) { normalizePitch = -speed; } } - pev->v_angle.x = cr::wrapAngle (curent + normalizePitch); if (pev->v_angle.x > 89.9f) { diff --git a/src/support.cpp b/src/support.cpp index 419db24..dea2869 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -204,7 +204,7 @@ bool BotSupport::isMonster (edict_t *ent) { return false; } - if (ent->v.classname.str ().startsWith ("hostage")) { + if (isHostageEntity (ent)) { return false; } @@ -226,6 +226,18 @@ bool BotSupport::isPlayerVIP (edict_t *ent) { return *(engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v'; } +bool BotSupport::isHostageEntity (edict_t *ent) { + if (game.isNullEntity (ent)) { + return false; + } + auto classHash = ent->v.classname.str ().hash (); + + constexpr auto kHostageEntity = StringRef::fnv1a32 ("hostage_entity"); + constexpr auto kMonsterScientist = StringRef::fnv1a32 ("monster_scientist"); + + return classHash == kHostageEntity || classHash == kMonsterScientist; +} + bool BotSupport::isFakeClient (edict_t *ent) { if (bots[ent] != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) { return true; diff --git a/src/tasks.cpp b/src/tasks.cpp index 8b6aab3..6d1d3ff 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -1471,7 +1471,7 @@ void Bot::pickupItem_ () { auto tab = conf.getRawWeapons (); - if ((tab[weaponIndex].id == Weapon::Shield || weaponIndex >= kPrimaryWeaponMinIndex || hasShield ()) && niceWeapon) { + if ((weaponIndex >= kPrimaryWeaponMinIndex || tab[weaponIndex].id == Weapon::Shield || hasShield ()) && niceWeapon) { selectWeaponByIndex (weaponIndex); dropCurrentWeapon (); } @@ -1561,9 +1561,7 @@ void Bot::pickupItem_ () { // find the nearest 'unused' hostage within the area game.searchEntities (pev->origin, 768.0f, [&] (edict_t *ent) { - auto classname = ent->v.classname.str (); - - if (!classname.startsWith ("hostage_entity") && !classname.startsWith ("monster_scientist")) { + if (!util.isHostageEntity (ent)) { return EntitySearchResult::Continue; } diff --git a/src/vision.cpp b/src/vision.cpp index 9e464cf..d546dc5 100644 --- a/src/vision.cpp +++ b/src/vision.cpp @@ -123,35 +123,37 @@ void Bot::updateAimDir () { } else if (flags & AimFlags::PredictPath) { bool changePredictedEnemy = true; - bool predictFailed = false; - if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy) { + if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { changePredictedEnemy = false; } - auto doFailPredict = [this] () { + auto doFailPredict = [this] () -> void { + if (m_timeNextTracking > game.time ()) { + return; // do not fail instantly + } m_aimFlags &= ~AimFlags::PredictPath; m_trackingEdict = nullptr; + m_lookAtPredict = nullptr; }; - if (changePredictedEnemy) { + auto isPredictedIndexApplicable = [this] () -> bool { int pathLength = m_lastPredictLength; int predictNode = m_lastPredictIndex; if (predictNode != kInvalidNodeIndex) { - TraceResult tr; - game.testLine (getEyesPos (), graph[predictNode].origin, TraceIgnore::Everything, ent (), &tr); - - if (tr.flFraction < 0.2f) { - pathLength = kInfiniteDistanceLong; + if (!vistab.visible (m_currentNodeIndex, predictNode)) { + predictNode = kInvalidNodeIndex; } } + return predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ (); + }; - if (predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ ()) { - m_lookAt = graph[predictNode].origin; - m_lookAtSafe = m_lookAt; + if (changePredictedEnemy) { + if (isPredictedIndexApplicable ()) { + m_lookAtPredict = graph[m_lastPredictIndex].origin; - m_timeNextTracking = game.time () + 0.25f; + m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f); m_trackingEdict = m_lastEnemy; // feel free to fire if shootable @@ -161,15 +163,16 @@ void Bot::updateAimDir () { } else { doFailPredict (); - predictFailed = true; + } + } + else { + if (!isPredictedIndexApplicable ()) { + doFailPredict (); } } - if (predictFailed) { - doFailPredict (); - } - else { - m_lookAt = m_lookAtSafe; + if (!m_lookAtPredict.empty ()) { + m_lookAt = m_lookAtPredict; } } else if (flags & AimFlags::Camp) { @@ -194,8 +197,9 @@ void Bot::updateAimDir () { else { m_lookAt = m_destOrigin; } + const bool onLadder = (m_pathFlags & NodeFlag::Ladder); - if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !(m_pathFlags & NodeFlag::Ladder)) { + if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !onLadder) { auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex); if (graph.exists (dangerIndex) && vistab.visible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) { @@ -211,8 +215,17 @@ void Bot::updateAimDir () { } } + // try look at next node if on ladder + if (onLadder && m_pathWalk.hasNext ()) { + auto nextPath = graph[m_pathWalk.next ()]; + + if ((nextPath.flags & NodeFlag::Ladder) && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (120.0f) && nextPath.origin.z > m_pathOrigin.z + 45.0f) { + m_lookAt = nextPath.origin; + } + } + // don't look at bottom of node, if reached it - if (m_lookAt == m_destOrigin) { + if (m_lookAt == m_destOrigin && !onLadder) { m_lookAt.z = getEyesPos ().z; } } @@ -230,14 +243,15 @@ void Bot::checkDarkness () { } // do not check every frame - if (m_checkDarkTime + 5.0f > game.time () || cr::fzero (m_path->light)) { + if (m_checkDarkTime > game.time () || cr::fzero (m_path->light)) { return; } + auto skyColor = illum.getSkyColor (); auto flashOn = (pev->effects & EF_DIMLIGHT); if (mp_flashlight.bool_ () && !m_hasNVG) { - auto task = Task (); + auto task = getCurrentTaskId (); if (!flashOn && task != Task::Camp && task != Task::Attack && m_heardSoundTime + 3.0f < game.time () && m_flashLevel > 30 && ((skyColor > 50.0f && m_path->light < 10.0f) || (skyColor <= 50.0f && m_path->light < 40.0f))) { pev->impulse = 100; @@ -257,7 +271,7 @@ void Bot::checkDarkness () { issueCommand ("nightvision"); } } - m_checkDarkTime = game.time (); + m_checkDarkTime = game.time () + rg.get (2.0f, 4.0f); } @@ -359,12 +373,12 @@ void Bot::updateLookAngles () { m_idealAngles.y = direction.y; } else { - float accel = cr::clamp (stiffness * angleDiffYaw - damping * m_lookYawVel, -accelerate, accelerate); + const float accel = cr::clamp (stiffness * angleDiffYaw - damping * m_lookYawVel, -accelerate, accelerate); m_lookYawVel += delta * accel; m_idealAngles.y += delta * m_lookYawVel; } - float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate); + const float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate); m_lookPitchVel += delta * accel; m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f); diff --git a/vc/yapb.vcxproj b/vc/yapb.vcxproj index a389406..6684ca8 100644 --- a/vc/yapb.vcxproj +++ b/vc/yapb.vcxproj @@ -40,6 +40,9 @@ + + + diff --git a/vc/yapb.vcxproj.filters b/vc/yapb.vcxproj.filters index 462bbc9..2b1a838 100644 --- a/vc/yapb.vcxproj.filters +++ b/vc/yapb.vcxproj.filters @@ -19,6 +19,9 @@ {f6a0fc04-bdf5-479b-8e5a-85eae698541e} + + {01281138-9315-450e-be71-55e16a2e3019} + @@ -168,6 +171,18 @@ inc\ext\crlib + + inc + + + inc\ext\crlib\simd + + + inc\ext\crlib\simd + + + inc\ext\crlib\simd +