fix: texts have been corrected. (#477)

task: normal: skip sniper node if no sniper weapon.
task: normal: don't walk if there are no enemies left and they are using knives.
task: attack: get proper function if bots lose close node after attack task.
nav: unnecessary codes cleared.
combat: attack movements reviewed.
bot: enemy hearing is now better. (thanks for the @spodlesniy idea)
desire: increased desire to seek cover by distance. (made for zombie plague mod)
---------
Co-authored-by: jeefo <jeefo@rwsh.ru>
This commit is contained in:
commandcobra7 2023-09-10 11:12:15 +03:00 committed by GitHub
commit ae9beff151
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 92 additions and 84 deletions

View file

@ -235,13 +235,6 @@ yb_pickup_best "1"
// //
yb_ignore_objectives "0" yb_ignore_objectives "0"
//
// Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby.
// ---
// Default: "1", Min: "0", Max: "1"
//
yb_random_knife_attacks "1"
// //
// Enables or disables bots chat functionality. // Enables or disables bots chat functionality.
// --- // ---
@ -681,6 +674,13 @@ yb_camping_time_min "15.0"
// //
yb_camping_time_max "45.0" yb_camping_time_max "45.0"
//
// Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby.
// ---
// Default: "1", Min: "0", Max: "1"
//
yb_random_knife_attacks "1"
// //
// Maximum number for path length, to predict the enemy. // Maximum number for path length, to predict the enemy.
// --- // ---

View file

@ -260,7 +260,7 @@ public:
void showFileInfo (); void showFileInfo ();
void emitNotify (int32_t sound); void emitNotify (int32_t sound);
IntArray getNarestInRadius (float radius, const Vector &origin, int maxCount = -1); IntArray getNearestInRadius (float radius, const Vector &origin, int maxCount = -1);
const IntArray &getNodesInBucket (const Vector &pos); const IntArray &getNodesInBucket (const Vector &pos);
public: public:

View file

@ -883,7 +883,6 @@ extern ConVar cv_shoots_thru_walls;
extern ConVar cv_debug; extern ConVar cv_debug;
extern ConVar cv_debug_goal; extern ConVar cv_debug_goal;
extern ConVar cv_save_bots_names; extern ConVar cv_save_bots_names;
extern ConVar cv_random_knife_attacks;
extern ConVar cv_rotate_bots; extern ConVar cv_rotate_bots;
extern ConVar cv_graph_url; extern ConVar cv_graph_url;
extern ConVar cv_graph_url_upload; extern ConVar cv_graph_url_upload;

View file

@ -38,7 +38,6 @@ ConVar cv_pickup_custom_items ("pickup_custom_items", "0", "Allows or disallows
ConVar cv_pickup_ammo_and_kits ("pickup_ammo_and_kits", "0", "Allows bots pickup mod items like ammo, health kits and suits."); ConVar cv_pickup_ammo_and_kits ("pickup_ammo_and_kits", "0", "Allows bots pickup mod items like ammo, health kits and suits.");
ConVar cv_pickup_best ("pickup_best", "1", "Allows or disallows bots to pickup best weapons."); ConVar cv_pickup_best ("pickup_best", "1", "Allows or disallows bots to pickup best weapons.");
ConVar cv_ignore_objectives ("ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and saves hostages."); ConVar cv_ignore_objectives ("ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and saves hostages.");
ConVar cv_random_knife_attacks ("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 // game console variables
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef); ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
@ -1684,7 +1683,7 @@ void Bot::refreshEnemyPredict () {
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) {
const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
if (distanceToLastEnemySq > cr::sqrf (384.0f) && (distanceToLastEnemySq < cr::sqrf (2048.0f) || usesSniper ())) { if (distanceToLastEnemySq > cr::sqrf (128.0f) && (distanceToLastEnemySq < cr::sqrf (2048.0f) || usesSniper ())) {
m_aimFlags |= AimFlags::PredictPath; m_aimFlags |= AimFlags::PredictPath;
} }
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f); const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f && distanceToLastEnemySq < cr::sqrf (256.0f);
@ -1902,6 +1901,9 @@ void Bot::filterTasks () {
else if (m_isVIP || m_isReloading || (sniping && usesSniper ())) { else if (m_isVIP || m_isReloading || (sniping && usesSniper ())) {
ratio *= 3.0f; // triple the seek cover desire if bot is VIP or reloading ratio *= 3.0f; // triple the seek cover desire if bot is VIP or reloading
} }
else if (m_lastEnemyOrigin.distance2d (pev->origin) < 200.0f) {
ratio *= 5.0f;
}
else if (m_isCreature) { else if (m_isCreature) {
ratio = 0.0f; ratio = 0.0f;
} }
@ -3354,12 +3356,12 @@ void Bot::updatePracticeValue (int damage) {
const auto health = static_cast <int> (m_healthValue); const auto health = static_cast <int> (m_healthValue);
// max goal value // max goal value
constexpr int maxGoalValue = PracticeLimit::Goal; constexpr int kMaxGoalValue = PracticeLimit::Goal;
// only rate goal node if bot died because of the damage // only rate goal node 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 // FIXME: could be done a lot better, however this cares most about damage done by sniping or really deadly weapons
if (health - damage <= 0) { if (health - damage <= 0) {
practice.setValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (practice.getValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - health / 20, -maxGoalValue, maxGoalValue)); practice.setValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (practice.getValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - health / 20, -kMaxGoalValue, kMaxGoalValue));
} }
} }
@ -3376,7 +3378,7 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
if (attackerTeam == victimTeam) { if (attackerTeam == victimTeam) {
return; return;
} }
constexpr int maxDamageValue = PracticeLimit::Damage; constexpr int kMaxDamageValue = PracticeLimit::Damage;
// if these are bots also remember damage to rank destination of the bot // if these are bots also remember damage to rank destination of the bot
m_goalValue -= static_cast <float> (damage); m_goalValue -= static_cast <float> (damage);
@ -3398,13 +3400,13 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) {
if (m_healthValue > 20.0f) { if (m_healthValue > 20.0f) {
if (victimTeam == Team::Terrorist || victimTeam == Team::CT) { if (victimTeam == Team::Terrorist || victimTeam == Team::CT) {
practice.setDamage (victimIndex, victimIndex, victimIndex, cr::clamp (practice.getDamage (victimTeam, victimIndex, victimIndex), 0, maxDamageValue)); practice.setDamage (victimIndex, victimIndex, victimIndex, cr::clamp (practice.getDamage (victimTeam, victimIndex, victimIndex), 0, kMaxDamageValue));
} }
} }
const auto updateDamage = util.isFakeClient (attacker) ? 10 : 7; const auto updateDamage = util.isFakeClient (attacker) ? 10 : 7;
// store away the damage done // store away the damage done
const auto damageValue = cr::clamp (practice.getDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, maxDamageValue); const auto damageValue = cr::clamp (practice.getDamage (m_team, victimIndex, attackerIndex) + damage / updateDamage, 0, kMaxDamageValue);
if (damageValue > practice.getHighestDamageForTeam (m_team)) { if (damageValue > practice.getHighestDamageForTeam (m_team)) {
practice.setHighestDamageForTeam (m_team, damageValue); practice.setHighestDamageForTeam (m_team, damageValue);
@ -3650,17 +3652,18 @@ bool Bot::isOutOfBombTimer () {
} }
void Bot::updateHearing () { void Bot::updateHearing () {
int hearEnemyIndex = kInvalidNodeIndex; if (game.is (GameFlags::FreeForAll)) {
return;
}
edict_t *hearedEnemy = nullptr;
float nearestDistanceSq = kInfiniteDistance; float nearestDistanceSq = kInfiniteDistance;
// setup potential visibility set from engine // setup potential visibility set from engine
auto set = game.getVisibilitySet (this, false); auto set = game.getVisibilitySet (this, false);
// loop through all enemy clients to check for hearable stuff // loop through all enemy clients to check for hearable stuff
for (int i = 0; i < game.maxClients (); ++i) { for (const auto &client : util.getClients ()) {
const auto &client = util.getClient (i); if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || !client.ent || client.noise.last < game.time ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.noise.last < game.time ()) {
continue; continue;
} }
@ -3674,18 +3677,13 @@ void Bot::updateHearing () {
} }
if (distanceSq < nearestDistanceSq) { if (distanceSq < nearestDistanceSq) {
hearEnemyIndex = i; hearedEnemy = client.ent;
nearestDistanceSq = distanceSq; nearestDistanceSq = distanceSq;
} }
} }
edict_t *player = nullptr;
if (hearEnemyIndex >= 0 && util.getClient (hearEnemyIndex).team != m_team && !game.is (GameFlags::FreeForAll)) {
player = util.getClient (hearEnemyIndex).ent;
}
// did the bot hear someone ? // did the bot hear someone ?
if (player != nullptr && util.isPlayer (player)) { if (hearedEnemy != nullptr && util.isPlayer (hearedEnemy)) {
// change to best weapon if heard something // change to best weapon if heard something
if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !isKnifeMode ()) { if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !isKnifeMode ()) {
selectBestWeapon (); selectBestWeapon ();
@ -3700,26 +3698,26 @@ void Bot::updateHearing () {
// didn't bot already have an enemy ? take this one... // didn't bot already have an enemy ? take this one...
if (m_lastEnemyOrigin.empty () || m_lastEnemy == nullptr) { if (m_lastEnemyOrigin.empty () || m_lastEnemy == nullptr) {
m_lastEnemy = player; m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = player->v.origin; m_lastEnemyOrigin = hearedEnemy->v.origin;
} }
// bot had an enemy, check if it's the heard one // bot had an enemy, check if it's the heard one
else { else {
if (player == m_lastEnemy) { if (hearedEnemy == m_lastEnemy) {
// bot sees enemy ? then bail out ! // bot sees enemy ? then bail out !
if (m_states & Sense::SeeingEnemy) { if (m_states & Sense::SeeingEnemy) {
return; return;
} }
m_lastEnemyOrigin = player->v.origin; m_lastEnemyOrigin = hearedEnemy->v.origin;
} }
else { else {
// if bot had an enemy but the heard one is nearer, take it instead // if bot had an enemy but the heard one is nearer, take it instead
const float distanceSq = m_lastEnemyOrigin.distanceSq (pev->origin); const float distanceSq = m_lastEnemyOrigin.distanceSq (pev->origin);
if (distanceSq > player->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) { if (distanceSq > hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) {
m_lastEnemy = player; m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = player->v.origin; m_lastEnemyOrigin = hearedEnemy->v.origin;
} }
else { else {
return; return;
@ -3728,9 +3726,9 @@ void Bot::updateHearing () {
} }
// check if heard enemy can be seen // check if heard enemy can be seen
if (checkBodyParts (player)) { if (checkBodyParts (hearedEnemy)) {
m_enemy = player; m_enemy = hearedEnemy;
m_lastEnemy = player; m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = m_enemyOrigin; m_lastEnemyOrigin = m_enemyOrigin;
m_states |= Sense::SeeingEnemy; m_states |= Sense::SeeingEnemy;
@ -3739,11 +3737,11 @@ void Bot::updateHearing () {
// check if heard enemy can be shoot through some obstacle // check if heard enemy can be shoot through some obstacle
else { else {
if (cv_shoots_thru_walls.bool_ () && m_difficulty >= Difficulty::Normal && m_lastEnemy == player && rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct) && m_seeEnemyTime + 3.0f > game.time () && isPenetrableObstacle (player->v.origin)) { if (cv_shoots_thru_walls.bool_ () && m_difficulty >= Difficulty::Normal && m_lastEnemy == hearedEnemy && rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct) && m_seeEnemyTime + 3.0f > game.time () && isPenetrableObstacle (hearedEnemy->v.origin)) {
m_enemy = player; m_enemy = hearedEnemy;
m_lastEnemy = player; m_lastEnemy = hearedEnemy;
m_enemyOrigin = player->v.origin; m_enemyOrigin = hearedEnemy->v.origin;
m_lastEnemyOrigin = player->v.origin; m_lastEnemyOrigin = hearedEnemy->v.origin;
m_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy); m_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy);
m_seeEnemyTime = game.time (); m_seeEnemyTime = game.time ();
@ -3840,7 +3838,7 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) {
} }
float Bot::getShiftSpeed () { float Bot::getShiftSpeed () {
if (getCurrentTaskId () == Task::SeekCover || (m_aimFlags & AimFlags::Enemy) || isDucking () || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK) || (m_currentTravelFlags & PathFlag::Jump) || (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || isInWater () || m_isStuck) { if (getCurrentTaskId () == Task::SeekCover || (m_aimFlags & AimFlags::Enemy) || isDucking () || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK) || (m_currentTravelFlags & PathFlag::Jump) || (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || isInWater () || isKnifeMode () || m_isStuck || m_numEnemiesLeft <= 0) {
return pev->maxspeed; return pev->maxspeed;
} }
return pev->maxspeed * 0.4f; return pev->maxspeed * 0.4f;

View file

@ -1161,11 +1161,14 @@ void Bot::attackMovement () {
else if (usesRifle () || usesSubmachine () || usesHeavy ()) { else if (usesRifle () || usesSubmachine () || usesHeavy ()) {
const int rand = rg.get (1, 100); const int rand = rg.get (1, 100);
if (distance < 500.0f) { if (distance < 768.0f) {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
} }
else if (distance < 1024.0f) { else if (distance < 1024.0f) {
if (rand < (usesSubmachine () ? 50 : 30)) { if (isGroupOfEnemies (m_enemy->v.origin)) {
m_fightStyle = Fight::Strafe;
}
else if (rand < (usesSubmachine () ? 50 : 30)) {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
} }
else { else {
@ -1173,7 +1176,10 @@ void Bot::attackMovement () {
} }
} }
else { else {
if (rand < (usesSubmachine () ? 80 : 90)) { if (isGroupOfEnemies (m_enemy->v.origin)) {
m_fightStyle = Fight::Strafe;
}
else if (rand < (usesSubmachine () ? 80 : 90)) {
m_fightStyle = Fight::Stay; m_fightStyle = Fight::Stay;
} }
else { else {
@ -1199,9 +1205,12 @@ void Bot::attackMovement () {
m_moveSpeed = -pev->maxspeed; m_moveSpeed = -pev->maxspeed;
} }
if (usesKnife ()) { if (usesKnife () && isInViewCone (m_enemy->v.origin)) {
m_fightStyle = Fight::None; m_fightStyle = Fight::Strafe;
m_lastFightStyleCheck = game.time (); }
if (usesPistol () && distance < 768.0f) {
m_fightStyle = Fight::Strafe;
} }
if (m_fightStyle == Fight::Strafe) { if (m_fightStyle == Fight::Strafe) {
@ -1270,7 +1279,7 @@ void Bot::attackMovement () {
} }
// do not move forward/backward is too far // do not move forward/backward is too far
if (distance > 1024.0) { if (distance > 1024.0f) {
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
} }
} }
@ -1280,7 +1289,7 @@ void Bot::attackMovement () {
if (alreadyDucking) { if (alreadyDucking) {
m_duckTime = game.time () + m_frameInterval * 2.0f; m_duckTime = game.time () + m_frameInterval * 2.0f;
} }
else if ((distance > kDoubleSprayDistance && hasPrimaryWeapon ()) && (m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { else if ((distance > 768.0f && hasPrimaryWeapon ()) && (m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) {
const int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); const int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
if (vistab.visible (m_currentNodeIndex, enemyNearestIndex, VisIndex::Crouch) && vistab.visible (enemyNearestIndex, m_currentNodeIndex, VisIndex::Crouch)) { if (vistab.visible (m_currentNodeIndex, enemyNearestIndex, VisIndex::Crouch) && vistab.visible (enemyNearestIndex, m_currentNodeIndex, VisIndex::Crouch)) {
@ -1293,7 +1302,7 @@ void Bot::attackMovement () {
if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) { if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) {
if (distance < 768.0f) { if (distance < 768.0f) {
if (rg.get (0, 1000) < rg.get (7, 12) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { if (rg.get (0, 1000) < rg.get (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
pev->button |= IN_JUMP; pev->button |= IN_JUMP;
} }
} }
@ -1308,7 +1317,7 @@ void Bot::attackMovement () {
Vector right, forward; Vector right, forward;
pev->v_angle.angleVectors (&forward, &right, nullptr); pev->v_angle.angleVectors (&forward, &right, nullptr);
const auto &front = right * m_moveSpeed * 0.2f; const auto &front = forward * m_moveSpeed * 0.2f;
const auto &side = right * m_strafeSpeed * 0.2f; const auto &side = right * m_strafeSpeed * 0.2f;
const auto &spot = pev->origin + front + side + pev->velocity * m_frameInterval; const auto &spot = pev->origin + front + side + pev->velocity * m_frameInterval;
@ -1907,7 +1916,7 @@ void Bot::checkGrenadesThrow () {
const float radius = cr::max (192.0f, m_lastEnemy->v.velocity.length2d ()); const float radius = cr::max (192.0f, m_lastEnemy->v.velocity.length2d ());
const Vector &pos = m_lastEnemy->v.velocity.get2d () + m_lastEnemy->v.origin; const Vector &pos = m_lastEnemy->v.velocity.get2d () + m_lastEnemy->v.origin;
auto predicted = graph.getNarestInRadius (radius, pos, 12); auto predicted = graph.getNearestInRadius (radius, pos, 12);
if (predicted.empty ()) { if (predicted.empty ()) {
m_states &= ~Sense::ThrowExplosive; m_states &= ~Sense::ThrowExplosive;

View file

@ -539,7 +539,7 @@ int BotGraph::getNearest (const Vector &origin, const float range, int flags) {
return index; return index;
} }
IntArray BotGraph::getNarestInRadius (float radius, const Vector &origin, int maxCount) { IntArray BotGraph::getNearestInRadius (float radius, const Vector &origin, int maxCount) {
// returns all nodes within radius from position // returns all nodes within radius from position
const float radiusSq = cr::sqrf (radius); const float radiusSq = cr::sqrf (radius);

View file

@ -429,9 +429,9 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
} }
m_hindrance = nullptr; m_hindrance = nullptr;
float distanceSq = cr::sqrf (348.0f); float distanceSq = cr::sqrf (384.0f);
if (getCurrentTaskId () == Task::Attack || isOnLadder () || isInNarrowPlace ()) { if (isOnLadder () || isInNarrowPlace ()) {
return; return;
} }
const auto ownPrio = bots.getPlayerPriority (ent ()); const auto ownPrio = bots.getPlayerPriority (ent ());
@ -610,19 +610,19 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
dirLeft = true; dirLeft = true;
} }
const auto &testDir = m_moveSpeed > 0.0f ? forward : -forward; const auto &testDir = m_moveSpeed > 0.0f ? forward : -forward;
constexpr float blockDistance = 32.0f; constexpr float kBlockDistance = 32.0f;
// now check which side is blocked // now check which side is blocked
src = pev->origin + right * blockDistance; src = pev->origin + right * kBlockDistance;
dst = src + testDir * blockDistance; dst = src + testDir * kBlockDistance;
game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr); game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr);
if (!cr::fequal (tr.flFraction, 1.0f)) { if (!cr::fequal (tr.flFraction, 1.0f)) {
blockedRight = true; blockedRight = true;
} }
src = pev->origin - right * blockDistance; src = pev->origin - right * kBlockDistance;
dst = src + testDir * blockDistance; dst = src + testDir * kBlockDistance;
game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr); game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr);
@ -714,8 +714,9 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
state[i] += 5; state[i] += 5;
} }
} }
else else {
state[i] = 0; state[i] = 0;
}
++i; ++i;
// weighted all possible moves, now sort them to start with most probable // weighted all possible moves, now sort them to start with most probable
@ -823,6 +824,7 @@ void Bot::moveToGoal () {
} }
} }
} }
m_lastUsedNodesTime = game.time ();
// special movement for swimming here // special movement for swimming here
if (isInWater ()) { if (isInWater ()) {
@ -959,7 +961,7 @@ bool Bot::updateNavigation () {
constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f); constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f);
m_pathOrigin = m_path->origin + kLadderOffset; m_pathOrigin = m_path->origin + kLadderOffset;
} }
else if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !(pev->flags & FL_DUCKING)) { else if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !isDucking ()) {
m_moveSpeed = pev->origin.distance (m_pathOrigin); m_moveSpeed = pev->origin.distance (m_pathOrigin);
if (m_moveSpeed < 150.0f) { if (m_moveSpeed < 150.0f) {
@ -1037,7 +1039,6 @@ bool Bot::updateNavigation () {
} }
} }
} }
} }
} }
@ -1173,13 +1174,13 @@ bool Bot::updateNavigation () {
// did we reach a destination node? // did we reach a destination node?
if (getTask ()->data == m_currentNodeIndex) { if (getTask ()->data == m_currentNodeIndex) {
if (m_chosenGoalIndex != kInvalidNodeIndex) { if (m_chosenGoalIndex != kInvalidNodeIndex) {
constexpr int maxGoalValue = PracticeLimit::Goal; constexpr int kMaxGoalValue = PracticeLimit::Goal;
// add goal values // add goal values
int goalValue = practice.getValue (m_team, m_chosenGoalIndex, m_currentNodeIndex); int goalValue = practice.getValue (m_team, m_chosenGoalIndex, m_currentNodeIndex);
const int addedValue = static_cast <int> (m_healthValue * 0.5f + m_goalValue * 0.5f); const int addedValue = static_cast <int> (m_healthValue * 0.5f + m_goalValue * 0.5f);
goalValue = cr::clamp (goalValue + addedValue, -maxGoalValue, maxGoalValue); goalValue = cr::clamp (goalValue + addedValue, -kMaxGoalValue, kMaxGoalValue);
// update the practice for team // update the practice for team
practice.setValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue); practice.setValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue);
@ -1782,7 +1783,7 @@ int Bot::findNearestNode () {
// try to search ANYTHING that can be reached // try to search ANYTHING that can be reached
if (!graph.exists (index)) { if (!graph.exists (index)) {
nearestDistanceSq = cr::sqrf (kMaxDistance); nearestDistanceSq = cr::sqrf (kMaxDistance);
const auto &nearestNodes = graph.getNarestInRadius (kMaxDistance, pev->origin); const auto &nearestNodes = graph.getNearestInRadius (kMaxDistance, pev->origin);
for (const auto &i : nearestNodes) { for (const auto &i : nearestNodes) {
const auto &path = graph[i]; const auto &path = graph[i];
@ -1809,7 +1810,6 @@ int Bot::findNearestNode () {
} }
} }
// worst case, take any node... // worst case, take any node...
if (!graph.exists (index)) { if (!graph.exists (index)) {
index = graph.getNearestNoBuckets (origin); index = graph.getNearestNoBuckets (origin);
@ -2174,7 +2174,7 @@ bool Bot::selectBestNextNode () {
continue; continue;
} }
// skip itn't connected links // skip isn't connected links
if (!graph.isConnected (link.index, nextNodeIndex) || !graph.isConnected (link.index, prevNodeIndex)) { if (!graph.isConnected (link.index, nextNodeIndex) || !graph.isConnected (link.index, prevNodeIndex)) {
continue; continue;
} }
@ -3095,7 +3095,6 @@ edict_t *Bot::lookupButton (StringRef target) {
return result; return result;
} }
bool Bot::isReachableNode (int index) { bool Bot::isReachableNode (int index) {
// this function return whether bot able to reach index node or not, depending on several factors. // this function return whether bot able to reach index node or not, depending on several factors.

View file

@ -13,6 +13,8 @@ ConVar cv_camping_allowed ("camping_allowed", "1", "Allows or disallows bots to
ConVar cv_camping_time_min ("camping_time_min", "15.0", "Lower bound of time from which time for camping is calculated", true, 5.0f, 90.0f); ConVar cv_camping_time_min ("camping_time_min", "15.0", "Lower bound of time from which time for camping is calculated", true, 5.0f, 90.0f);
ConVar cv_camping_time_max ("camping_time_max", "45.0", "Upper bound of time until which time for camping is calculated", true, 15.0f, 120.0f); ConVar cv_camping_time_max ("camping_time_max", "45.0", "Upper bound of time until which time for camping is calculated", true, 15.0f, 120.0f);
ConVar cv_random_knife_attacks ("random_knife_attacks", "1", "Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby.");
void Bot::normal_ () { void Bot::normal_ () {
m_aimFlags |= AimFlags::Nav; m_aimFlags |= AimFlags::Nav;
@ -95,6 +97,11 @@ void Bot::normal_ () {
campingAllowed = false; campingAllowed = false;
} }
// skip sniper node if we don't have sniper weapon
if (campingAllowed && !usesSniper () && (m_pathFlags & NodeFlag::Sniper)) {
campingAllowed = false;
}
if (campingAllowed) { if (campingAllowed) {
// crouched camping here? // crouched camping here?
if (m_pathFlags & NodeFlag::Crouch) { if (m_pathFlags & NodeFlag::Crouch) {
@ -446,8 +453,12 @@ void Bot::attackEnemy_ () {
} }
else { else {
completeTask (); completeTask ();
findNextBestNode ();
if (!m_lastEnemyOrigin.empty ()) {
m_destOrigin = m_lastEnemyOrigin; m_destOrigin = m_lastEnemyOrigin;
} }
}
m_navTimeset = game.time (); m_navTimeset = game.time ();
} }
@ -1034,7 +1045,7 @@ void Bot::followUser_ () {
// didn't choose goal node yet? // didn't choose goal node yet?
if (!hasActiveGoal ()) { if (!hasActiveGoal ()) {
int destIndex = graph.getNearest (m_targetEntity->v.origin); int destIndex = graph.getNearest (m_targetEntity->v.origin);
auto points = graph.getNarestInRadius (200.0f, m_targetEntity->v.origin); auto points = graph.getNearestInRadius (200.0f, m_targetEntity->v.origin);
for (auto &newIndex : points) { for (auto &newIndex : points) {
// if node not yet used, assign it as dest // if node not yet used, assign it as dest
@ -1560,7 +1571,7 @@ void Bot::pickupItem_ () {
int nearestHostageNodeIndex = kInvalidNodeIndex; int nearestHostageNodeIndex = kInvalidNodeIndex;
// find the nearest 'unused' hostage within the area // find the nearest 'unused' hostage within the area
game.searchEntities (pev->origin, 768.0f, [&] (edict_t *ent) { game.searchEntities (pev->origin, 1024.0f, [&] (edict_t *ent) {
if (!util.isHostageEntity (ent)) { if (!util.isHostageEntity (ent)) {
return EntitySearchResult::Continue; return EntitySearchResult::Continue;
} }

View file

@ -155,11 +155,6 @@ void Bot::updateAimDir () {
m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f); m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f);
m_trackingEdict = m_lastEnemy; m_trackingEdict = m_lastEnemy;
// feel free to fire if shootable
if (!usesSniper () && lastEnemyShootable ()) {
m_wantsToFire = true;
}
} }
else { else {
doFailPredict (); doFailPredict ();
@ -195,9 +190,6 @@ void Bot::updateAimDir () {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
} }
} }
else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()) {
m_lookAt = m_lastEnemyOrigin;
}
else { else {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
} }