nav: fall controls are not good. (#656)

nav: fall controls are not good.
nav: increased behavior rate in finding the best goal.
nav: avoiding the door after hitting it.
combat: if the enemy is using a sniper rifle, move.
combat: enemy group functionality has been improved.
combat: if we are doing a camping task, do not switch to a knife at close range.
aim: slight changes to enemy prediction.

---------

Co-authored-by: jeefo <dmitry@jeefo.net>
This commit is contained in:
commandcobra7 2025-01-09 21:53:16 +03:00 committed by GitHub
commit e717710bd1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 245 additions and 211 deletions

View file

@ -85,7 +85,7 @@ void Bot::avoidGrenades () {
const auto &activeGrenades = bots.getActiveGrenades ();
// find all grenades on the map
for (auto pent : activeGrenades) {
for (const auto &pent : activeGrenades) {
if (pent->v.effects & EF_NODRAW) {
continue;
}
@ -96,10 +96,7 @@ void Bot::avoidGrenades () {
}
auto model = pent->v.model.str (9);
if (m_preventFlashing < game.time ()
&& cv_whose_your_daddy
&& model == kFlashbangModelName) {
if (m_preventFlashing < game.time () && model == kFlashbangModelName) {
// don't look at flash bang
if (!(m_states & Sense::SeeingEnemy)) {
m_lookAt.y = cr::wrapAngle ((game.getEntityOrigin (pent) - getEyesPos ()).angles ().y + 180.0f);
@ -180,9 +177,7 @@ void Bot::checkBreakablesAround () {
|| !game.hasBreakables ()
|| m_seeEnemyTime + 4.0f > game.time ()
|| !game.isNullEntity (m_enemy)
|| !hasPrimaryWeapon ()
|| (m_aimFlags & (AimFlags::Danger | AimFlags::PredictPath | AimFlags::Enemy))
|| getCurrentTaskId () != Task::Normal) {
|| !hasPrimaryWeapon ()) {
return;
}
const auto radius = cv_object_destroy_radius.as <float> ();
@ -349,13 +344,13 @@ void Bot::updatePickups () {
bool itemExists = false;
auto pickupItem = m_pickupItem;
for (auto &ent : interesting) {
for (const auto &ent : interesting) {
// in the periods of updating interesting entities we can get fake ones, that already were picked up, so double check if drawn
if (ent->v.effects & EF_NODRAW) {
continue;
}
const Vector &origin = game.getEntityOrigin (ent);
const auto &origin = game.getEntityOrigin (ent);
// too far from us ?
if (pev->origin.distanceSq (origin) > radiusSq) {
@ -595,7 +590,7 @@ void Bot::updatePickups () {
m_defendedBomb = true;
const int index = findDefendNode (origin);
const Path &path = graph[index];
const auto &path = graph[index];
const float bombTimer = mp_c4timer.as <float> ();
const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin);
@ -638,7 +633,7 @@ void Bot::updatePickups () {
// don't steal hostage from human teammate (hack)
if (allowPickup) {
for (auto &client : util.getClients ()) {
for (const 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::sqrf (240.0f)) {
allowPickup = false;
@ -805,9 +800,9 @@ Vector Bot::getCampDirection (const Vector &dest) {
float nearestDistance = kInfiniteDistance;
int lookAtNode = kInvalidNodeIndex;
const Path &path = graph[tempIndex];
const auto &path = graph[tempIndex];
for (auto &link : path.links) {
for (const auto &link : path.links) {
if (link.index == kInvalidNodeIndex) {
continue;
}
@ -1658,7 +1653,8 @@ void Bot::overrideConditions () {
const auto tid = getCurrentTaskId ();
// check if we need to escape from bomb
if (game.mapIs (MapFlags::Demolition)
if ((tid == Task::Normal || tid == Task::MoveToPosition)
&& game.mapIs (MapFlags::Demolition)
&& bots.isBombPlanted ()
&& m_isAlive
&& tid != Task::EscapeFromBomb
@ -1717,8 +1713,8 @@ void Bot::overrideConditions () {
}
// special handling for reloading
if (!bots.isRoundOver () &&
tid == Task::Normal
if (!bots.isRoundOver ()
&& tid == Task::Normal
&& m_reloadState != Reload::None
&& m_isReloading
&& !isDucking ()
@ -1775,7 +1771,7 @@ void Bot::syncUpdatePredictedIndex () {
const float distToBotSq = botOrigin.distanceSq (graph[index].origin);
if (vistab.visible (currentNodeIndex, index) && distToBotSq > 128.0f && distToBotSq < cr::sqrf (768.0f)) {
if (vistab.visible (currentNodeIndex, index) && distToBotSq < cr::sqrf (2048.0f)) {
bestIndex = index;
return false;
}
@ -1812,7 +1808,8 @@ void Bot::refreshEnemyPredict () {
&& distanceToLastEnemySq < cr::sqrf (256.0f)
&& m_shootTime + 1.5f > game.time ();
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger))
&& !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
m_aimFlags |= AimFlags::LastEnemy;
}
}
@ -1993,8 +1990,8 @@ void Bot::filterTasks () {
// increase/decrease fear/aggression if bot uses a sniping weapon to be more careful
if (usesSniper ()) {
tempFear = tempFear * 1.2f;
tempAgression = tempAgression * 0.6f;
tempFear = tempFear * 1.5f;
tempAgression = tempAgression * 0.5f;
}
auto &filter = bots.getFilters ();
@ -2675,7 +2672,7 @@ void Bot::checkRadioQueue () {
switch (getCurrentTaskId ()) {
case Task::Normal:
if (getTask ()->data != kInvalidNodeIndex && rg.chance (70)) {
const Path &path = graph[getTask ()->data];
const auto &path = graph[getTask ()->data];
if (path.flags & NodeFlag::Goal) {
if (m_hasC4) {
@ -2787,7 +2784,7 @@ void Bot::checkRadioQueue () {
int bombPoint = kInvalidNodeIndex;
// find nearest bomb node to player
for (auto &point : graph.m_goalPoints) {
for (const auto &point : graph.m_goalPoints) {
distanceSq = graph[point].origin.distanceSq (m_radioEntity->v.origin);
if (distanceSq < nearestDistanceSq) {
@ -2932,7 +2929,7 @@ void Bot::frame () {
}
if (bots.isBombPlanted () && m_team == Team::CT && m_isAlive) {
const Vector &bombPosition = graph.getBombOrigin ();
const auto &bombPosition = graph.getBombOrigin ();
if (!m_hasProgressBar
&& getCurrentTaskId () != Task::EscapeFromBomb
@ -3057,9 +3054,9 @@ void Bot::logicDuringFreezetime () {
return;
}
if (rg.chance (15) && m_jumpTime + rg (1.0f, 2.0f) < game.time ()) {
if (rg.chance (15) && m_jumpTime < game.time ()) {
pev->button |= IN_JUMP;
m_jumpTime = game.time ();
m_jumpTime = game.time () + rg (1.0f, 2.0f);
}
static Array <edict_t *> players {};
players.clear ();
@ -3189,7 +3186,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 = 4.0f; // length of different vector (distance bot moved)
resetMovement ();
@ -3251,7 +3248,7 @@ void Bot::logic () {
else if (!hasFriendNearby
&& rg.chance (50)
&& game.getTeam (m_enemy) != m_team
&& isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) {
&& isGroupOfEnemies (m_enemy->v.origin)) {
pushChatterMessage (Chatter::ScaredEmotion);
}
@ -3303,8 +3300,8 @@ void Bot::logic () {
setIdealReactionTimers ();
// calculate 2 direction vectors, 1 without the up/down component
const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval);
const Vector &dirNormal = dirOld.normalize2d_apx ();
const auto &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval);
const auto &dirNormal = dirOld.normalize2d_apx ();
m_moveAngles = dirOld.angles ();
m_moveAngles.clampAngles ();
@ -3340,8 +3337,17 @@ void Bot::logic () {
// don't duck to get away faster
pev->button &= ~IN_DUCK;
m_moveSpeed = -pev->maxspeed;
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
Vector right {}, forward {};
pev->v_angle.angleVectors (&forward, &right, nullptr);
const auto &front = forward * -pev->maxspeed * 0.2f;
const auto &side = right * pev->maxspeed * static_cast <float> (m_needAvoidGrenade) * 0.2f;
const auto &spot = pev->origin + front + side + pev->velocity * m_frameInterval;
if (!isDeadlyMove (spot)) {
m_moveSpeed = -pev->maxspeed;
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
}
}
// ensure we're not stuck picking something
@ -3836,7 +3842,7 @@ Vector Bot::isBombAudible () {
if (m_difficulty > Difficulty::Hard) {
return graph.getBombOrigin ();
}
const Vector &bombOrigin = graph.getBombOrigin ();
const auto &bombOrigin = graph.getBombOrigin ();
const float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.as <float> ()) * 100.0f;
float desiredRadius = 768.0f;
@ -3865,8 +3871,8 @@ Vector Bot::isBombAudible () {
bool Bot::canRunHeavyWeight () {
constexpr auto kInterval = 1.0f / 10.0f;
if (m_heavyTimestamp + kInterval < game.time ()) {
m_heavyTimestamp = game.time ();
if (m_heavyTimestamp < game.time ()) {
m_heavyTimestamp = game.time () + kInterval;
return true;
}
@ -3981,7 +3987,7 @@ void Bot::updateHearing () {
float nearestDistanceSq = kInfiniteDistance;
// do not hear to other enemies if just tracked old one
if (m_timeNextTracking < game.time () && util.isAlive (m_lastEnemy) && m_lastEnemy == m_trackingEdict) {
if (m_timeNextTracking < game.time () && m_lastEnemy == m_trackingEdict && util.isAlive (m_lastEnemy)) {
m_hearedEnemy = m_lastEnemy;
m_lastEnemyOrigin = m_lastEnemy->v.origin;

View file

@ -133,10 +133,11 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) {
if (!cv_check_darkness && game.isNullEntity (enemy)) {
return false;
}
const auto &v = enemy->v;
const auto scolor = illum.getSkyColor ();
// check if node near the enemy have a degraded light level
const auto enemyNodeIndex = graph.getNearest (enemy->v.origin);
const auto enemyNodeIndex = graph.getNearest (v.origin);
if (!graph.exists (enemyNodeIndex)) {
return false;
@ -149,9 +150,9 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) {
}
bool enemySemiTransparent = false;
const bool enemyHasGun = (enemy->v.weapons & kPrimaryWeaponMask) || (enemy->v.weapons & kSecondaryWeaponMask);
const bool enemyHasFlashlightEnabled = !!(enemy->v.effects & EF_DIMLIGHT);
const bool enemyIsAttacking = !!(enemy->v.oldbuttons & IN_ATTACK);
const bool enemyHasGun = (v.weapons & kPrimaryWeaponMask) || (v.weapons & kSecondaryWeaponMask);
const bool enemyIsAttacking = (v.button & IN_ATTACK) || (v.oldbuttons & IN_ATTACK);
const bool enemyHasFlashlightEnabled = !!(v.effects & EF_DIMLIGHT);
if (!m_usesNVG && ((llevel < 3.0f && scolor > 50.0f) || (llevel < 25.0f && scolor <= 50.0f))
&& !enemyHasFlashlightEnabled && (!enemyIsAttacking || !enemyHasGun)) {
@ -163,7 +164,7 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) {
enemySemiTransparent = true;
}
TraceResult result {};
game.testLine (getEyesPos (), enemy->v.origin, m_isCreature ? TraceIgnore::None : TraceIgnore::Everything, ent (), &result);
game.testLine (getEyesPos (), v.origin, m_isCreature ? TraceIgnore::None : TraceIgnore::Everything, ent (), &result);
return (result.flFraction <= 1.0f && result.pHit == enemy && (m_usesNVG || !enemySemiTransparent));
}
@ -193,7 +194,7 @@ bool Bot::checkBodyPartsWithOffsets (edict_t *target) {
auto self = pev->pContainingEntity;
// creatures can't hurt behind anything
const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ?TraceIgnore::Monsters : TraceIgnore::Everything);
const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ? TraceIgnore::Monsters : TraceIgnore::Everything);
const auto hitsTarget = [&] () -> bool {
return result.flFraction >= 1.0f || result.pHit == target;
@ -390,7 +391,7 @@ bool Bot::lookupEnemies () {
m_states |= Sense::SuspectEnemy;
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f
&& pev->origin.distanceSq2d (m_lastEnemyOrigin) < cr::sqrf (256.0f)
&& m_lastEnemyOrigin.distanceSq (pev->origin) < cr::sqrf (256.0f)
&& m_shootTime + 1.5f > game.time ();
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger))
@ -1244,10 +1245,9 @@ void Bot::fireWeapons () {
if (cv_stab_close_enemies && m_difficulty >= Difficulty::Normal
&& m_healthValue > 80.0f
&& !game.isNullEntity (m_enemy)
&& m_healthValue >= m_enemy->v.health
&& distance < 100.0f
&& !isOnLadder ()
&& !isGroupOfEnemies (pev->origin)) {
&& !isGroupOfEnemies (pev->origin)
&& getCurrentTaskId () != Task::Camp) {
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
return;
@ -1428,13 +1428,14 @@ void Bot::attackMovement () {
approach = 49;
}
}
const bool isEnemyCone = isInViewCone (m_enemy->v.origin);
// only take cover when bomb is not planted and enemy can see the bot or the bot is VIP
if (!game.is (GameFlags::CSDM) && !isKnifeMode ()) {
if ((m_states & Sense::SeeingEnemy)
&& approach < 30
&& !bots.isBombPlanted ()
&& (isInViewCone (m_enemy->v.origin) || m_isVIP || m_isReloading)) {
&& (isEnemyCone || m_isVIP || m_isReloading)) {
if (m_retreatTime < game.time ()) {
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
@ -1467,10 +1468,7 @@ void Bot::attackMovement () {
m_fightStyle = Fight::Strafe;
}
else if (distanceSq < cr::sqrf (1024.0f)) {
if (isGroupOfEnemies (m_enemy->v.origin)) {
m_fightStyle = Fight::Strafe;
}
else if (rand < (usesSubmachine () ? 50 : 30)) {
if (rand < (usesSubmachine () ? 50 : 30)) {
m_fightStyle = Fight::Strafe;
}
else {
@ -1478,10 +1476,7 @@ void Bot::attackMovement () {
}
}
else {
if (isGroupOfEnemies (m_enemy->v.origin)) {
m_fightStyle = Fight::Strafe;
}
else if (rand < (usesSubmachine () ? 80 : 90)) {
if (rand < (usesSubmachine () ? 80 : 90)) {
m_fightStyle = Fight::Stay;
}
else {
@ -1505,7 +1500,12 @@ void Bot::attackMovement () {
// fire hurts friend value here is from previous frame, but acceptable, and saves us alot of cpu cycles
if (approach < 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ())
&& distanceSq < cr::sqrf (pistolStrafeDistance)
&& isInViewCone (m_enemyOrigin))) {
&& isEnemyCone)) {
m_fightStyle = Fight::Strafe;
}
const auto enemyWeaponIsSniper = (m_enemy->v.weapons & kSniperWeaponMask);
if (enemyWeaponIsSniper && isEnemyCone) {
m_fightStyle = Fight::Strafe;
}
m_lastFightStyleCheck = game.time () + 3.0f;
@ -1515,7 +1515,7 @@ void Bot::attackMovement () {
m_moveSpeed = -pev->maxspeed;
}
if (usesKnife () && isInViewCone (m_enemy->v.origin)) {
if (usesKnife () && isEnemyCone) {
m_fightStyle = Fight::Strafe;
}
@ -1624,7 +1624,7 @@ void Bot::attackMovement () {
if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) {
if (distanceSq < cr::sqrf (kSprayDistanceX2)) {
if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isEnemyCone) {
pev->button |= IN_JUMP;
}
}
@ -2003,28 +2003,29 @@ void Bot::updateTeamCommands () {
m_timeTeamOrder = game.time () + rg (15.0f, 30.0f);
}
bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) {
bool Bot::isGroupOfEnemies (const Vector &location) {
int numPlayers = 0;
// needs a square radius
const float radiusSq = cr::sqrf (radius);
const float radiusSq = cr::sqrf (768.0f);
// search the world for enemy players...
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team || client.ent == ent ()) {
continue;
}
if (client.ent->v.origin.distanceSq (location) < radiusSq) {
if (client.team == m_team) {
return false; // don't target our teammates...
}
if (numPlayers++ > numEnemies) {
return true;
if (!seesEntity (client.origin)) {
continue;
}
++numPlayers;
}
}
if (numPlayers < 2) {
return false;
}
return false;
}
@ -2541,7 +2542,7 @@ bool Bot::isEnemyNoticeable (float range) {
if (isCrouching) {
// crouching and motionless - very tough to notice
closeChance = 80.0f;
farChance = 5.0f; // takes about three seconds to notice (50% chance)
farChance = 5.0f; // takes about three seconds to notice (50% chance)
}
// standing
else {

View file

@ -1509,6 +1509,7 @@ void Bot::newRound () {
m_lastBreakable = nullptr;
m_timeDoorOpen = 0.0f;
m_timeHitDoor = 0.0f;
for (auto &fall : m_checkFallPoint) {
fall.clear ();
@ -1624,7 +1625,6 @@ void Bot::newRound () {
m_dodgeStrafeDir = Dodge::None;
m_fightStyle = Fight::None;
m_lastFightStyleCheck = 0.0f;
m_stuckTimestamp = 0.0f;
m_checkWeaponSwitch = true;
m_checkKnifeSwitch = true;
@ -2209,11 +2209,11 @@ void BotThreadWorker::startup (int workers) {
m_botWorker.startup (static_cast <size_t> (requestedThreads));
}
bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, float grenadeBloat) {
bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
if (m_activeGrenades.empty ()) {
return false;
}
constexpr auto kSmokeGrenadeRadius = 115;
constexpr auto kSmokeGrenadeRadius = 115.0f;
// distance along line of sight covered by smoke
float totalSmokedLength = 0.0f;
@ -2241,7 +2241,7 @@ bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, flo
continue;
}
const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius) * cr::sqrf (grenadeBloat);
const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius);
const Vector &smokeOrigin = game.getEntityOrigin (pent);
Vector toGrenade = smokeOrigin - from;

View file

@ -99,6 +99,7 @@ int Bot::findBestGoal () {
}
return findGoalPost (hasMoreHostagesAround ? GoalTactic::Goal : GoalTactic::RescueHostage, defensiveNodes, offensiveNodes);
}
constexpr float kBehaviorBase = 30.0f;
const auto difficulty = static_cast <float> (m_difficulty);
offensive = m_agressionLevel * 100.0f;
@ -107,27 +108,27 @@ int Bot::findBestGoal () {
if (game.mapIs (MapFlags::Assassination | MapFlags::HostageRescue)) {
if (m_team == Team::Terrorist) {
if (m_personality == Personality::Rusher) {
defensive -= 25.0f - difficulty * 0.5f;
offensive += 25.0f + difficulty * 5.0f;
defensive -= kBehaviorBase - difficulty * 0.5f;
offensive += kBehaviorBase + difficulty * 5.0f;
}
else if (m_personality == Personality::Normal && rg.chance (40)) {
defensive -= 25.0f;
offensive += 25.0f;
defensive -= kBehaviorBase;
offensive += kBehaviorBase;
}
else {
defensive += 25.0f;
offensive -= 25.0f;
defensive += kBehaviorBase;
offensive -= kBehaviorBase;
}
}
else if (m_team == Team::CT) {
// on hostage maps force more bots to save hostages
if (game.mapIs (MapFlags::HostageRescue)) {
defensive -= 25.0f - difficulty * 0.5f;
offensive += 25.0f + difficulty * 5.0f;
defensive -= kBehaviorBase - difficulty * 0.5f;
offensive += kBehaviorBase + difficulty * 5.0f;
}
else {
defensive -= 25.0f;
offensive += 25.0f;
defensive -= kBehaviorBase;
offensive += kBehaviorBase;
}
}
}
@ -140,8 +141,8 @@ int Bot::findBestGoal () {
}
return m_chosenGoalIndex = findBombNode ();
}
defensive += 25.0f + difficulty * 4.0f;
offensive -= 25.0f - difficulty * 0.5f;
defensive += kBehaviorBase + difficulty * 5.0f;
offensive -= kBehaviorBase - difficulty * 0.5f;
if (m_personality != Personality::Rusher) {
defensive += 10.0f;
@ -155,12 +156,12 @@ int Bot::findBestGoal () {
}
else if (game.mapIs (MapFlags::Escape)) {
if (m_team == Team::Terrorist) {
offensive += 25.0f + difficulty * 4.0f;
defensive -= 25.0f - difficulty * 0.5f;
offensive += kBehaviorBase + difficulty * 5.0f;
defensive -= kBehaviorBase - difficulty * 0.5f;
}
else if (m_team == Team::CT) {
offensive -= 25.0f - difficulty * 4.5f;
defensive += 25.0f + difficulty * 0.5f;
offensive -= kBehaviorBase - difficulty * 5.0f;
defensive += kBehaviorBase + difficulty * 0.5f;
}
}
@ -188,6 +189,7 @@ int Bot::findBestGoal () {
}
if (goalDesire > tacticChoice) {
tacticChoice = goalDesire;
tactic = GoalTactic::Goal;
}
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
@ -230,7 +232,7 @@ int Bot::findBestGoalWhenBombAction () {
m_defendedBomb = true;
result = findDefendNode (bombOrigin);
const Path &path = graph[result];
const auto &path = graph[result];
const float bombTimer = mp_c4timer.as <float> ();
const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin);
@ -258,7 +260,11 @@ int Bot::findBestGoalWhenBombAction () {
}
int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
int goalChoices[4] {};
for (int i = 0; i < 4; ++i) {
goalChoices[i] = kInvalidNodeIndex;
}
if (tactic == GoalTactic::Defensive && !(*defensive).empty ()) { // careful goal
postProcessGoals (*defensive, goalChoices);
@ -282,7 +288,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
float nearestDistanceSq = kInfiniteDistance;
int count = 0;
for (auto &point : graph.m_goalPoints) {
for (const auto &point : graph.m_goalPoints) {
const float distanceSq = graph[point].origin.distanceSq (pev->origin);
if (distanceSq > cr::sqrf (1024.0f)) {
@ -313,7 +319,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
float nearestDistanceSq = kInfiniteDistance;
int count = 0;
for (auto &point : graph.m_rescuePoints) {
for (const auto &point : graph.m_rescuePoints) {
const float distanceSq = graph[point].origin.distanceSq (pev->origin);
if (distanceSq < nearestDistanceSq) {
@ -334,10 +340,6 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
}
ensureCurrentNodeIndex ();
if (goalChoices[0] == kInvalidNodeIndex) {
return m_chosenGoalIndex = graph.random ();
}
// rusher bots does not care any danger (idea from pbmm)
if (m_personality == Personality::Rusher) {
const auto randomGoal = goalChoices[rg (0, 3)];
@ -468,39 +470,23 @@ void Bot::ignoreCollision () {
}
void Bot::doPlayerAvoidance (const Vector &normal) {
if (cv_has_team_semiclip || game.is (GameFlags::FreeForAll)) {
if (isOnLadder () || cv_has_team_semiclip || game.is (GameFlags::FreeForAll)) {
return; // no player avoiding when with semiclip plugin
}
m_hindrance = nullptr;
float distanceSq = cr::sqrf (512.0f);
if (isOnLadder ()) {
return;
}
const auto ownPrio = bots.getPlayerPriority (ent ());
// find nearest player to bot
for (const auto &client : util.getClients ()) {
// need only good meat
if (!(client.flags & ClientFlags::Used)) {
continue;
}
// and still alive meet
if (!(client.flags & ClientFlags::Alive)) {
continue;
}
// our team, alive and not myself?
if (client.team != m_team || client.ent == ent ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) {
continue;
}
const auto otherPrio = bots.getPlayerPriority (client.ent);
// give some priorities to bot avoidance
if (ownPrio > otherPrio) {
if (ownPrio < otherPrio) {
continue;
}
@ -509,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;
}
}
@ -525,7 +511,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
if (game.isNullEntity (m_hindrance)) {
return;
}
const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f);
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
Vector right {}, forward {};
@ -551,10 +537,13 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
setStrafeSpeed (normal, -pev->maxspeed);
}
if (distanceSq < cr::sqrf (80.0f)) {
if (distanceSq < cr::sqrf (96.0f)) {
if ((dir | forward.normalize2d_apx ()) < 0.0f) {
m_moveSpeed = -pev->maxspeed;
}
else {
m_moveSpeed = pev->maxspeed;
}
}
}
}
@ -568,7 +557,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 < 4.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) {
m_prevTime = game.time (); // then consider being stuck
m_isStuck = true;
@ -862,7 +851,7 @@ void Bot::checkFall () {
}
}
else if (!isOnLadder () && !isInWater ()) {
if (!m_checkFallPoint[0].empty () && !m_checkFallPoint[1].empty ()) {
if (!m_checkFallPoint[0].empty () || !m_checkFallPoint[1].empty ()) {
m_checkFall = true;
}
}
@ -882,12 +871,12 @@ void Bot::checkFall () {
&& baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
fixFall = true;
}
else if (pev->origin.z + 128.0f < m_checkFallPoint[1].z && pev->origin.z + 128.0f < m_checkFallPoint[0].z) {
else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f || m_checkFallPoint[0].z > pev->origin.z + 128.0f) {
fixFall = true;
}
else if (m_currentNodeIndex != kInvalidNodeIndex
&& nowDistanceSq <= cr::sqrf (32.0f)
&& pev->origin.z + 64.0f < m_checkFallPoint[1].z) {
&& nowDistanceSq > cr::sqrf (16.0f)
&& m_checkFallPoint[1].z > pev->origin.z + 62.0f) {
fixFall = true;
}
@ -925,6 +914,26 @@ void Bot::moveToGoal () {
pev->button |= IN_DUCK;
}
}
// press jump button if we need to leave the ladder
if (!(m_pathFlags & NodeFlag::Ladder)
&& isPreviousLadder ()
&& isOnFloor ()
&& isOnLadder ()
&& m_moveSpeed > 50.0f
&& pev->velocity.lengthSq () < 50.0f) {
pev->button |= IN_JUMP;
m_jumpTime = game.time () + 1.0f;
}
const auto distanceSq2d = m_destOrigin.distanceSq2d (pev->origin + pev->velocity * m_frameInterval);
if (distanceSq2d < cr::sqrf (m_moveSpeed) * m_frameInterval && getTask ()->data != kInvalidNodeIndex) {
m_moveSpeed = distanceSq2d;
}
if (m_moveSpeed > pev->maxspeed) {
m_moveSpeed = pev->maxspeed;
}
m_lastUsedNodesTime = game.time ();
// special movement for swimming here
@ -1083,8 +1092,10 @@ bool Bot::updateNavigation () {
m_approachingLadderTimer.start (m_frameInterval * 4.0f);
}
if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !isDucking ()) {
m_moveSpeed = ladderDistance;
if (!isOnLadder () && isOnFloor () && !isDucking ()) {
if (!isPreviousLadder ()) {
m_moveSpeed = ladderDistance;
}
if (m_moveSpeed < 150.0f) {
m_moveSpeed = 150.0f;
@ -1107,17 +1118,14 @@ bool Bot::updateNavigation () {
bool foundGround = false;
if (cr::abs (pev->origin.z - client.ent->v.origin.z) > 15.0f) {
if (isPreviousLadder ()) {
if (client.ent->v.origin.distanceSq (pev->origin) < cr::sqrf (64.0f)) {
foundGround = true;
}
}
if (!isPreviousLadder ()
&& client.ent->v.origin.distanceSq (pev->origin) < cr::sqrf (128.0f)
&& cr::abs (client.ent->v.origin.z - pev->origin.z) > 17.0f) {
foundGround = true;
}
if (foundGround) {
clearSearchNodes ();
findNextBestNode ();
changeNodeIndex (prevNodeIndex);
}
}
}
@ -1140,8 +1148,11 @@ bool Bot::updateNavigation () {
game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr);
if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && util.isDoorEntity (tr.pHit)) {
const auto &origin = game.getEntityOrigin (tr.pHit);
const float distanceSq = pev->origin.distanceSq (origin);
// if the door is near enough...
if (pev->origin.distanceSq (game.getEntityOrigin (tr.pHit)) < cr::sqrf (50.0f)) {
if (distanceSq < cr::sqrf (56.0f)) {
ignoreCollision (); // don't consider being stuck
// also 'use' the door randomly
@ -1175,8 +1186,8 @@ bool Bot::updateNavigation () {
}
// if bot hits the door, then it opens, so wait a bit to let it open safely
if (pev->velocity.lengthSq2d () < cr::sqrf (4.0f) && m_timeDoorOpen < game.time ()) {
if (getCurrentTaskId () != Task::MoveToPosition) {
if (pev->velocity.lengthSq2d () < cr::sqrf (10.0f) && m_timeDoorOpen < game.time ()) {
if (m_timeHitDoor >= 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
@ -1205,10 +1216,25 @@ bool Bot::updateNavigation () {
else if (m_tryOpenDoor > 4) {
m_tryOpenDoor = 0;
// go back to prev node, if blocked for long time
if (graph.exists (m_previousNodes[0])) {
startTask (Task::MoveToPosition, TaskPri::MoveToPosition, m_previousNodes[0], game.time () + 3.0f, true);
if (rg.chance (50)) {
const auto prevNode = m_previousNodes[0];
// go back to prev node, if blocked for long time
if (graph.exists (prevNode)) {
changeNodeIndex (prevNode);
}
}
else {
const auto &dirToPoint = (pev->origin - origin).normalize2d_apx ();
const auto &forwardMove = m_moveAngles.forward ().normalize2d_apx ();
if (distanceSq < cr::sqrf (80.0f)) {
if ((dirToPoint | forwardMove) < 0.0f && !checkWallOnBehind ()) {
m_moveSpeed = -pev->maxspeed;
}
}
}
m_timeHitDoor = game.time () + 3.0f;
}
}
}
@ -1307,7 +1333,7 @@ bool Bot::updateNavigation () {
&& getCurrentTaskId () != Task::EscapeFromBomb
&& taskTarget != kInvalidNodeIndex) {
const Vector &bombOrigin = isBombAudible ();
const auto &bombOrigin = isBombAudible ();
// bot within 'hearable' bomb tick noises?
if (!bombOrigin.empty ()) {
@ -1404,7 +1430,7 @@ bool Bot::updateLiftHandling () {
}
else if (!m_pathWalk.empty ()) { // no lift found at node
if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor) && m_pathWalk.hasNext ()) {
int nextNode = m_pathWalk.next ();
const auto nextNode = m_pathWalk.next ();
if (graph.exists (nextNode) && (graph[nextNode].flags & NodeFlag::Lift)) {
game.testLine (m_path->origin, graph[nextNode].origin, TraceIgnore::Everything, ent (), &tr);
@ -1727,8 +1753,13 @@ bool Bot::findNextBestNode () {
int busyIndex = kInvalidNodeIndex;
float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance };
int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
float lessDist[3] {};
int lessIndex[3] {};
for (int i = 0; i < 3; ++i) {
lessDist[i] = kInfiniteDistance;
lessIndex[i] = kInvalidNodeIndex;
}
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
const auto &bucket = graph.getNodesInBucket (origin);
@ -1785,19 +1816,10 @@ bool Bot::findNextBestNode () {
const float distanceSq = pev->origin.distanceSq (path.origin);
if (distanceSq < lessDist[0]) {
lessDist[2] = lessDist[1];
lessIndex[2] = lessIndex[1];
lessDist[1] = lessDist[0];
lessIndex[1] = lessIndex[0];
lessDist[0] = distanceSq;
lessIndex[0] = path.number;
}
else if (distanceSq < lessDist[1]) {
lessDist[2] = lessDist[1];
lessIndex[2] = lessIndex[1];
lessDist[1] = distanceSq;
lessIndex[1] = path.number;
}
@ -1888,7 +1910,7 @@ void Bot::findValidNode () {
damageValue = cr::clamp (damageValue + 100, 0, kMaxDamageValue);
// affect nearby connected with victim nodes
for (auto &neighbour : m_path->links) {
for (const auto &neighbour : m_path->links) {
if (graph.exists (neighbour.index)) {
int neighbourValue = practice.getDamage (team, neighbour.index, neighbour.index);
neighbourValue = cr::clamp (neighbourValue + 100, 0, kMaxDamageValue);
@ -2019,7 +2041,7 @@ int Bot::findBombNode () {
float lastDistanceSq = kInfiniteDistance;
// find nearest goal node either to bomb (if "heard" or player)
for (auto &point : goals) {
for (const auto &point : goals) {
const float distanceSq = bomb.distanceSq (graph[point].origin);
// check if we got more close distance
@ -2211,7 +2233,7 @@ int Bot::findCoverNode (float maxDistance) {
}
// now get enemies neigbouring points
for (auto &link : graph[enemyIndex].links) {
for (const auto &link : graph[enemyIndex].links) {
if (link.index != kInvalidNodeIndex) {
enemies.push (link.index);
}
@ -2228,7 +2250,7 @@ int Bot::findCoverNode (float maxDistance) {
}
bool neighbourVisible = false; // now check neighbour nodes for visibility
for (auto &enemy : enemies) {
for (const auto &enemy : enemies) {
if (vistab.visible (enemy, path.number)) {
neighbourVisible = true;
break;
@ -2422,7 +2444,7 @@ bool Bot::advanceMovement () {
m_campButtons = 0;
const int nextIndex = m_pathWalk.next ();
const auto nextIndex = m_pathWalk.next ();
auto kills = static_cast <float> (practice.getDamage (m_team, nextIndex, nextIndex));
// if damage done higher than one
@ -2452,7 +2474,7 @@ bool Bot::advanceMovement () {
}
// force terrorist bot to plant bomb
if (m_inBombZone && !m_hasProgressBar && m_hasC4 && getCurrentTaskId () != Task::PlantBomb) {
if (m_inBombZone && !m_hasProgressBar && m_hasC4 && tid != Task::PlantBomb) {
changeNextGoal ();
return false;
}
@ -2462,7 +2484,7 @@ bool Bot::advanceMovement () {
if (!m_pathWalk.empty ()) {
m_jumpSequence = false;
const int destIndex = m_pathWalk.first ();
const auto destIndex = m_pathWalk.first ();
bool isCurrentJump = false;
// find out about connection flags
@ -3158,13 +3180,13 @@ int Bot::getRandomCampDir () {
}
void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) {
const Vector &los = (moveDir - pev->origin).normalize2d_apx ();
const auto &los = (moveDir - pev->origin).normalize2d_apx ();
const float dot = los | pev->angles.forward ().get2d ();
if (dot > 0.0f && !checkWallOnRight ()) {
m_strafeSpeed = strafeSpeed;
}
else if (!checkWallOnLeft ()) {
else if (dot < 0.0f && !checkWallOnLeft ()) {
m_strafeSpeed = -strafeSpeed;
}
}
@ -3371,15 +3393,10 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
fprintf (stderr, "%s source path is same as dest (%d).\n", __func__, destIndex);
return;
}
clearSearchNodes ();
m_chosenGoalIndex = srcIndex;
m_goalValue = 0.0f;
// always use shortest-path algorithm when failed sanity checks within load
if (planner.isPathsCheckFailed ()) {
findShortestPath (srcIndex, destIndex);
return;
}
@ -3414,6 +3431,7 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
m_planner->setG (PlannerHeuristic::gfunctionPathDist);
}
}
clearSearchNodes ();
m_chosenGoalIndex = srcIndex;
m_goalValue = 0.0f;

View file

@ -222,7 +222,12 @@ bool BotSupport::isDoorEntity (edict_t *ent) {
if (game.isNullEntity (ent)) {
return false;
}
return ent->v.classname.str ().startsWith ("func_door");
const auto classHash = ent->v.classname.str ().hash ();
constexpr auto kFuncDoor = StringRef::fnv1a32 ("func_door");
constexpr auto kFuncDoorRotating = StringRef::fnv1a32 ("func_door_rotating");
return classHash == kFuncDoor || classHash == kFuncDoorRotating;
}
bool BotSupport::isHostageEntity (edict_t *ent) {
@ -241,6 +246,14 @@ bool BotSupport::isShootableBreakable (edict_t *ent) {
if (game.isNullEntity (ent) || ent == game.getStartEntity ()) {
return false;
}
// todo: move the breakables list into own array, and refresh them every round, since next thing is very expensive
#if 0
StringRef material = engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "material");
if (material == "7") {
return false;
}
#endif
const auto limit = cv_breakable_health_limit.as <float> ();
// not shootable

View file

@ -148,6 +148,12 @@ void Bot::normal_ () {
campingAllowed = false;
}
// if the bot is about to come to the camp spot, but there is already someone else camping
if (!campingAllowed && getTask ()->data == m_currentNodeIndex && getTask ()->data != kInvalidNodeIndex) {
clearSearchNodes ();
getTask ()->data = kInvalidNodeIndex;
}
if (campingAllowed) {
// crouched camping here?
if (m_pathFlags & NodeFlag::Crouch) {
@ -427,7 +433,7 @@ void Bot::seekCover_ () {
startTask (Task::Hide, TaskPri::Hide, kInvalidNodeIndex, game.time () + rg (3.0f, 12.0f), false);
// get a valid look direction
const Vector &dest = getCampDirection (m_lastEnemyOrigin);
const auto &dest = getCampDirection (m_lastEnemyOrigin);
m_aimFlags |= AimFlags::Camp;
m_lookAtSafe = dest;
@ -641,18 +647,18 @@ void Bot::camp_ () {
// random camp dir, or prediction
auto useRandomCampDirOrPredictEnemy = [&] () {
if (!m_lastEnemyOrigin.empty ()) {
if (!m_lastEnemyOrigin.empty () && util.isAlive (m_lastEnemy)) {
auto pathLength = m_lastPredictLength;
auto predictNode = m_lastPredictIndex;
if (pathLength > 1 && isNodeValidForPredict (predictNode)) {
if (isNodeValidForPredict (predictNode) && pathLength > 1) {
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
}
else {
pathLength = 0;
predictNode = findAimingNode (m_lastEnemyOrigin, pathLength);
if (pathLength > 1 && isNodeValidForPredict (predictNode)) {
if (isNodeValidForPredict (predictNode) && pathLength > 1) {
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
}
}
@ -681,7 +687,7 @@ void Bot::camp_ () {
TraceResult tr {};
// and use real angles to check it
auto to = m_pathOrigin + dest.forward () * 500.0f;
const auto &to = m_pathOrigin + dest.forward () * 500.0f;
// let's check the destination
game.testLine (getEyesPos (), to, TraceIgnore::Monsters, ent (), &tr);
@ -886,7 +892,7 @@ void Bot::defuseBomb_ () {
defuseRemainingTime = fullDefuseTime - game.time ();
}
const Vector &bombPos = graph.getBombOrigin ();
const auto &bombPos = graph.getBombOrigin ();
bool defuseError = false;
// exception: bomb has been defused
@ -1001,11 +1007,11 @@ void Bot::defuseBomb_ () {
botStandOrigin = pev->origin;
}
const float duckLengthSq = m_entity.distanceSq (botDuckOrigin);
const float standLengthSq = m_entity.distanceSq (botStandOrigin);
const float duckDistanceSq = m_entity.distanceSq (botDuckOrigin);
const float standDistanceSq = m_entity.distanceSq (botStandOrigin);
if (duckLengthSq > cr::sqrf (75.0f) || standLengthSq > cr::sqrf (75.0f)) {
if (standLengthSq < duckLengthSq) {
if (duckDistanceSq > cr::sqrf (75.0f) || standDistanceSq > cr::sqrf (75.0f)) {
if (standDistanceSq < duckDistanceSq) {
m_duckDefuse = false; // stand
}
else {
@ -1126,7 +1132,7 @@ void Bot::followUser_ () {
int destIndex = graph.getNearest (m_targetEntity->v.origin);
auto points = graph.getNearestInRadius (200.0f, m_targetEntity->v.origin);
for (auto &newIndex : points) {
for (const auto &newIndex : points) {
// if node not yet used, assign it as dest
if (newIndex != m_currentNodeIndex && !isOccupiedNode (newIndex)) {
destIndex = newIndex;
@ -1398,10 +1404,7 @@ void Bot::escapeFromBomb_ () {
pev->button |= IN_ATTACK2;
}
if (!usesKnife ()
&& m_lastEnemyOrigin.empty ()
&& !(m_states & Sense::SeeingEnemy)
&& !util.isAlive (m_lastEnemy)) {
if (!usesKnife () && game.isNullEntity (m_enemy) && !util.isAlive (m_lastEnemy)) {
selectWeaponById (Weapon::Knife);
}
@ -1457,7 +1460,6 @@ void Bot::escapeFromBomb_ () {
}
void Bot::shootBreakable_ () {
// breakable destroyed?
if (!util.isShootableBreakable (m_breakableEntity)) {
completeTask ();
@ -1471,12 +1473,12 @@ void Bot::shootBreakable_ () {
m_lookAtSafe = m_breakableOrigin;
// is bot facing the breakable?
if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.95f && util.isVisible (m_breakableOrigin, ent ())) {
m_aimFlags |= AimFlags::Override;
if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.90f) {
m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
m_aimFlags |= AimFlags::Override;
if (usesKnife ()) {
selectBestWeapon ();
}
@ -1501,7 +1503,7 @@ void Bot::pickupItem_ () {
return;
}
const Vector &dest = game.getEntityOrigin (m_pickupItem);
const auto &dest = game.getEntityOrigin (m_pickupItem);
m_destOrigin = dest;
m_entity = dest;
@ -1669,7 +1671,7 @@ void Bot::pickupItem_ () {
}
// check if hostage is with a human teammate (hack)
for (auto &client : util.getClients ()) {
for (const 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::sqrf (240.0f)) {
return EntitySearchResult::Continue;

View file

@ -361,9 +361,9 @@ bool Frustum::isObjectInsidePlane (const Plane &plane, const Vector &center, flo
return plane.result + (plane.normal | point) >= 0.0f;
};
const Vector &test = plane.normal.get2d ();
const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
const auto &test = plane.normal.get2d ();
const auto &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
const auto &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
return isPointInsidePlane (top) || isPointInsidePlane (bottom);
}
@ -403,7 +403,7 @@ void Frustum::calculate (Planes &planes, const Vector &viewAngle, const Vector &
bool Frustum::check (const Planes &planes, edict_t *ent) const {
constexpr auto kOffset = Vector (0.0f, 0.0f, 5.0f);
const Vector &origin = ent->v.origin - kOffset;
const auto &origin = ent->v.origin - kOffset;
for (const auto &plane : planes) {
if (!isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) {
@ -507,26 +507,25 @@ void Bot::setAimDirection () {
auto predictNode = m_lastPredictIndex;
auto isPredictedIndexApplicable = [&] () -> bool {
if (!isNodeValidForPredict (predictNode)
|| pathLength >= cv_max_nodes_for_predict.as <int> ()) {
TraceResult result {};
game.testLine (getEyesPos (), graph[predictNode].origin + pev->view_ofs, TraceIgnore::None, ent (), &result);
if (result.flFraction < 0.5f) {
return false;
}
const float distToPredictNodeSq = graph[predictNode].origin.distanceSq (pev->origin);
if (distToPredictNodeSq >= cr::sqrf (2048.0f)) {
return false;
}
if (!vistab.visible (m_currentNodeIndex, predictNode)
|| !vistab.visible (m_previousNodes[0], predictNode)
|| !vistab.visible (predictNode, m_currentNodeIndex)) {
if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) {
predictNode = kInvalidNodeIndex;
pathLength = kInfiniteDistanceLong;
return false;
}
TraceResult result {};
game.testLine (getEyesPos (), graph[predictNode].origin + pev->view_ofs, TraceIgnore::None, ent (), &result);
return result.flFraction >= 0.8f && graph[predictNode].origin.distanceSq (pev->origin) > cr::sqrf (256.0f);
return isNodeValidForPredict (predictNode) && pathLength < cv_max_nodes_for_predict.as <int> ();
};
if (changePredictedEnemy) {