diff --git a/inc/yapb.h b/inc/yapb.h index 468d51e..71b26fc 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -1177,6 +1177,7 @@ extern ConVar cv_shoots_thru_walls; extern ConVar cv_debug; extern ConVar cv_debug_goal; extern ConVar cv_save_bots_names; +extern ConVar cv_random_knife_attacks; extern ConVar mp_freezetime; extern ConVar mp_roundtime; diff --git a/src/botlib.cpp b/src/botlib.cpp index 6e85a31..a638a5c 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -35,6 +35,7 @@ ConVar cv_restricted_weapons ("yb_restricted_weapons", "", "Specifies semicolon ConVar cv_attack_monsters ("yb_attack_monsters", "0", "Allows or disallows bots to attack monsters."); ConVar cv_pickup_custom_items ("yb_pickup_custom_items", "0", "Allows or disallows bots to pickup custom items."); ConVar cv_ignore_objectives ("yb_ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and saves hostages."); +ConVar cv_random_knife_attacks ("yb_random_knife_attacks", "1", "Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby."); // game console variables ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef); @@ -95,7 +96,7 @@ bool Bot::seesItem (const Vector &destination, const char *classname) { game.testLine (getEyesPos (), destination, TraceIgnore::None, ent (), &tr); // check if line of sight to object is not blocked (i.e. visible) - if (tr.flFraction >= 0.95f && tr.pHit && tr.pHit != game.getStartEntity ()) { + if (tr.flFraction < 1.0f && tr.pHit) { return strcmp (tr.pHit->v.classname.chars (), classname) == 0; } return true; @@ -557,7 +558,7 @@ void Bot::updatePickups () { // check if line of sight to object is not blocked (i.e. visible) if (seesItem (origin, classname)) { - if (strncmp ("hostage_entity", classname, 14) == 0) { + if (strncmp ("hostage_entity", classname, 14) == 0 || strncmp ("monster_scientist", classname, 17) == 0) { allowPickup = true; pickupType = Pickup::Hostage; } @@ -735,6 +736,17 @@ void Bot::updatePickups () { } } } + + // don't steal hostage from human teammate (hack) + if (allowPickup) { + for (auto &client : util.getClients ()) { + if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) && + client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::square (240.0f)) { + allowPickup = false; + break; + } + } + } } } else if (pickupType == Pickup::PlantedC4) { @@ -3057,7 +3069,7 @@ void Bot::normal_ () { } // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) - if (usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { + if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { if (rg.chance (40)) { pev->button |= IN_ATTACK; } @@ -4588,6 +4600,61 @@ void Bot::pickupItem_ () { } m_hostages.push (m_pickupItem); m_pickupItem = nullptr; + completeTask (); + + float minDistance = kInfiniteDistance; + int nearestHostageNodeIndex = kInvalidNodeIndex; + + // find the nearest 'unused' hostage within the area + game.searchEntities (pev->origin, 768.0f, [&] (edict_t *ent) { + auto classname = ent->v.classname.chars (); + + if (strncmp ("hostage_entity", classname, 14) != 0 && strncmp ("monster_scientist", classname, 17) != 0) { + return EntitySearchResult::Continue; + } + + // check if hostage is dead + if (game.isNullEntity (ent) || ent->v.health <= 0) { + return EntitySearchResult::Continue; + } + + // check if hostage is with a bot + for (const auto &other : bots) { + if (other->m_notKilled) { + for (const auto &hostage : other->m_hostages) { + if (hostage == ent) { + return EntitySearchResult::Continue; + } + } + } + } + + // check if hostage is with a human teammate (hack) + for (auto &client : util.getClients ()) { + if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) && + client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::square (240.0f)) { + return EntitySearchResult::Continue; + } + } + + int hostageNodeIndex = graph.getNearest (ent->v.origin); + + if (graph.exists (hostageNodeIndex)) { + float distance = graph[hostageNodeIndex].origin.distanceSq (pev->origin); + + if (distance < minDistance) { + minDistance = distance; + nearestHostageNodeIndex = hostageNodeIndex; + } + } + + return EntitySearchResult::Continue; + }); + + if (nearestHostageNodeIndex != kInvalidNodeIndex) { + clearTask (Task::MoveToPosition); // remove any move tasks + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, nearestHostageNodeIndex, 0.0f, true); + } } ignoreCollision (); // also don't consider being stuck } @@ -5074,7 +5141,7 @@ bool Bot::hasHostage () { return false; } - for (auto hostage : m_hostages) { + for (auto &hostage : m_hostages) { if (!game.isNullEntity (hostage)) { // don't care about dead hostages diff --git a/src/engine.cpp b/src/engine.cpp index 97a77a4..6801aab 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -123,7 +123,7 @@ void Game::levelInitialize (edict_t *entities, int max) { else if (strcmp (classname, "func_vip_safetyzone") == 0 || strcmp (classname, "info_vip_safetyzone") == 0) { m_mapFlags |= MapFlags::Assassination; // assassination map } - else if (strcmp (classname, "hostage_entity") == 0) { + else if (strcmp (classname, "hostage_entity") == 0 || strcmp (classname, "monster_scientist") == 0) { m_mapFlags |= MapFlags::HostageRescue; // rescue map } else if (strcmp (classname, "func_bomb_target") == 0 || strcmp (classname, "info_bomb_target") == 0) { diff --git a/src/graph.cpp b/src/graph.cpp index a55a890..0282ead 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -2751,6 +2751,7 @@ void BotGraph::addBasic () { autoCreateForEntity (NodeAddFlag::Goal, "func_bomb_target"); // bombspot zone autoCreateForEntity (NodeAddFlag::Goal, "info_bomb_target"); // bombspot zone (same as above) autoCreateForEntity (NodeAddFlag::Goal, "hostage_entity"); // hostage entities + autoCreateForEntity (NodeAddFlag::Goal, "monster_scientist"); // hostage entities (same as above) autoCreateForEntity (NodeAddFlag::Goal, "func_vip_safetyzone"); // vip rescue (safety) zone autoCreateForEntity (NodeAddFlag::Goal, "func_escapezone"); // terrorist escape zone } diff --git a/src/navigate.cpp b/src/navigate.cpp index 80b46ee..e94ca91 100644 --- a/src/navigate.cpp +++ b/src/navigate.cpp @@ -79,9 +79,7 @@ int Bot::findBestGoal () { return findGoalPost (tactic, defensiveNodes, offensiveNodes); } else if (m_team == Team::CT && hasHostage ()) { - tactic = 2; - offensiveNodes = &graph.m_rescuePoints; - + tactic = 4; return findGoalPost (tactic, defensiveNodes, offensiveNodes); } @@ -208,6 +206,31 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) { postprocessGoals (graph.m_goalPoints, goalChoices); } } + else if (tactic == 4 && !graph.m_rescuePoints.empty ()) // rescue goal + { + // force ct with hostage(s) to select closest rescue goal + float minDist = kInfiniteDistance; + int count = 0; + + for (auto &point : graph.m_rescuePoints) { + float distance = graph[point].origin.distanceSq (pev->origin); + + if (distance < minDist) { + goalChoices[count] = point; + + if (++count > 3) { + count = 0; + } + minDist = distance; + } + } + + for (auto &choice : goalChoices) { + if (choice == kInvalidNodeIndex) { + choice = graph.m_rescuePoints.random (); + } + } + } if (!graph.exists (m_currentNodeIndex)) { m_currentNodeIndex = changePointIndex (findNearestNode ());