Merge branch 'master' into chatter-fixes-and-improvements

This commit is contained in:
Владислав Сухов 2024-05-02 20:02:11 +00:00
commit 09dbba82cf
9 changed files with 218 additions and 91 deletions

View file

@ -2205,6 +2205,12 @@ The name of setinfo key used to store password to bot commands and menus.
[TRANSLATED] [TRANSLATED]
Имя ключа setinfo используемого для хранения пароля доступа к командам и меню бота. Имя ключа setinfo используемого для хранения пароля доступа к командам и меню бота.
[ORIGINAL]
Allows to use classic bot kill on issuing end-round command in menus, instead of gamedll endround.
[TRANSLATED]
Позволяет использовать классическое убийство ботов при выполнении команды завершения раунда в меню, вместо использования функции завершения раунда от игровой (dll) библиотеки.
[ORIGINAL] [ORIGINAL]
Enables or disables CSDM / FFA mode for bots. Enables or disables CSDM / FFA mode for bots.
Allowed values: '0', '1', '2', '3'. Allowed values: '0', '1', '2', '3'.

@ -1 +1 @@
Subproject commit ea7a91bd042599132e60482f51087d9669dbe1c4 Subproject commit c72215707a9be44eb2582b82a90a4a7f27a9f2e1

View file

@ -132,7 +132,7 @@ public:
~PathWalk () = default; ~PathWalk () = default;
public: public:
int32_t &doubleNext () { int32_t &nextX2 () {
return at (2); return at (2);
} }
@ -307,6 +307,7 @@ private:
bool m_isCreature {}; // bot is not a player, but something else ? zombie ? bool m_isCreature {}; // bot is not a player, but something else ? zombie ?
bool m_defuseNotified {}; // bot is notified about bomb defusion bool m_defuseNotified {}; // bot is notified about bomb defusion
bool m_jumpSequence {}; // next path link will be jump link bool m_jumpSequence {}; // next path link will be jump link
bool m_checkFall {}; // check bot fall
PathWalk m_pathWalk {}; // pointer to current node from path PathWalk m_pathWalk {}; // pointer to current node from path
Dodge m_dodgeStrafeDir {}; // direction to strafe Dodge m_dodgeStrafeDir {}; // direction to strafe
@ -325,6 +326,7 @@ private:
edict_t *m_targetEntity {}; // the entity that the bot is trying to reach edict_t *m_targetEntity {}; // the entity that the bot is trying to reach
edict_t *m_avoidGrenade {}; // pointer to grenade entity to avoid edict_t *m_avoidGrenade {}; // pointer to grenade entity to avoid
edict_t *m_hindrance {}; // the hindrance edict_t *m_hindrance {}; // the hindrance
edict_t *m_hearedEnemy {}; // the heared enemy
Vector m_liftTravelPos {}; // lift travel position Vector m_liftTravelPos {}; // lift travel position
Vector m_moveAngles {}; // bot move angles Vector m_moveAngles {}; // bot move angles
@ -344,6 +346,7 @@ private:
Vector m_desiredVelocity {}; // desired velocity for jump nodes Vector m_desiredVelocity {}; // desired velocity for jump nodes
Vector m_breakableOrigin {}; // origin of breakable Vector m_breakableOrigin {}; // origin of breakable
Vector m_rightRef {}; // right referential vector Vector m_rightRef {}; // right referential vector
Vector m_checkFallPoint[2] {}; // check fall point
Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables
Array <edict_t *> m_hostages {}; // pointer to used hostage entities Array <edict_t *> m_hostages {}; // pointer to used hostage entities
@ -416,6 +419,7 @@ private:
bool isOutOfBombTimer (); bool isOutOfBombTimer ();
bool isWeaponBadAtDistance (int weaponIndex, float distance); bool isWeaponBadAtDistance (int weaponIndex, float distance);
bool needToPauseFiring (float distance); bool needToPauseFiring (float distance);
bool checkZoom (float distance);
bool lookupEnemies (); bool lookupEnemies ();
bool isEnemyHidden (edict_t *enemy); bool isEnemyHidden (edict_t *enemy);
bool isEnemyInvincible (edict_t *enemy); bool isEnemyInvincible (edict_t *enemy);
@ -464,6 +468,7 @@ private:
void updatePickups (); void updatePickups ();
void ensureEntitiesClear (); void ensureEntitiesClear ();
void checkTerrain (float movedDistance, const Vector &dirNormal); void checkTerrain (float movedDistance, const Vector &dirNormal);
void checkFall ();
void checkDarkness (); void checkDarkness ();
void checkParachute (); void checkParachute ();
void updatePracticeValue (int damage); void updatePracticeValue (int damage);

View file

@ -91,7 +91,7 @@ void Bot::avoidGrenades () {
} }
// check if visible to the bot // check if visible to the bot
if (!seesEntity (pent->v.origin) && isInFOV (pent->v.origin - getEyesPos ()) > pev->fov * 0.5f) { if (isInFOV (pent->v.origin - getEyesPos ()) > pev->fov * 0.5f && !seesEntity (pent->v.origin)) {
continue; continue;
} }
auto model = pent->v.model.str (9); auto model = pent->v.model.str (9);
@ -133,11 +133,11 @@ void Bot::avoidGrenades () {
} }
} }
else if (cv_smoke_grenade_checks.as <int> () == 1 && (pent->v.flags & FL_ONGROUND) && model == kSmokeModelName) { else if (cv_smoke_grenade_checks.as <int> () == 1 && (pent->v.flags & FL_ONGROUND) && model == kSmokeModelName) {
if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov / 3.0f) { if (seesEntity (pent->v.origin) && isInFOV (pent->v.origin - getEyesPos ()) < pev->fov / 3.0f) {
const auto &entOrigin = game.getEntityOrigin (pent); const auto &entOrigin = game.getEntityOrigin (pent);
const auto &betweenUs = (entOrigin - pev->origin).normalize_apx (); const auto &betweenUs = (entOrigin - pev->origin).normalize_apx ();
const auto &betweenNade = (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 (); const auto &betweenResult = ((betweenNade.get2d () * 150.0f + entOrigin) - pev->origin).normalize_apx ();
if ((betweenNade | betweenUs) > (betweenNade | betweenResult) && util.isVisible (pent->v.origin, ent ())) { if ((betweenNade | betweenUs) > (betweenNade | betweenResult) && util.isVisible (pent->v.origin, ent ())) {
const float distance = entOrigin.distance (pev->origin); const float distance = entOrigin.distance (pev->origin);
@ -751,9 +751,6 @@ void Bot::updatePickups () {
} }
void Bot::ensureEntitiesClear () { void Bot::ensureEntitiesClear () {
if ((!m_isStuck || m_navTimeset + getEstimatedNodeReachTime () + m_frameInterval * 2.0f > game.time ()) && !(m_states & Sense::SeeingEnemy)) {
return;
}
const auto tid = getCurrentTaskId (); const auto tid = getCurrentTaskId ();
if (tid == Task::PickupItem || (m_states & Sense::PickupItem)) { if (tid == Task::PickupItem || (m_states & Sense::PickupItem)) {
@ -1685,8 +1682,10 @@ void Bot::overrideConditions () {
// special handling for sniping // special handling for sniping
if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy))
&& m_sniperStopTime > game.time () && m_shootTime - 0.4f <= game.time ()
&& tid != Task::SeekCover) { && m_sniperStopTime > game.time ()) {
ignoreCollision ();
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;
@ -1897,8 +1896,7 @@ void Bot::setConditions () {
} }
// don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman)
if (!cv_ignore_enemies if (m_soundUpdateTime < game.time ()
&& m_soundUpdateTime < game.time ()
&& m_blindTime < game.time () && m_blindTime < game.time ()
&& m_seeEnemyTime + 1.0f < game.time ()) { && m_seeEnemyTime + 1.0f < game.time ()) {
@ -3214,8 +3212,11 @@ void Bot::logic () {
checkTerrain (movedDistance, dirNormal); checkTerrain (movedDistance, dirNormal);
} }
// if we have fallen from the place of move, the nearest point is allowed
checkFall ();
// check the darkness // check the darkness
if (cv_check_darkness) { if (!m_isCreature && cv_check_darkness) {
checkDarkness (); checkDarkness ();
} }
@ -3227,7 +3228,11 @@ void Bot::logic () {
m_moveSpeed = -pev->maxspeed; m_moveSpeed = -pev->maxspeed;
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade); m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
} }
ensureEntitiesClear ();
// ensure we're not stuck destroying/picking something
if (m_navTimeset + getEstimatedNodeReachTime () + 1.0f < game.time () && !(m_states & Sense::SeeingEnemy) && m_moveToGoal) {
ensureEntitiesClear ();
}
// check if need to use parachute // check if need to use parachute
checkParachute (); checkParachute ();
@ -3850,10 +3855,10 @@ bool Bot::isOutOfBombTimer () {
} }
void Bot::updateHearing () { void Bot::updateHearing () {
if (game.is (GameFlags::FreeForAll) || m_enemyIgnoreTimer > game.time ()) { if (game.is (GameFlags::FreeForAll) || m_enemyIgnoreTimer > game.time () || cv_ignore_enemies) {
return; return;
} }
edict_t *hearedEnemy = nullptr; m_hearedEnemy = nullptr;
float nearestDistanceSq = kInfiniteDistance; float nearestDistanceSq = kInfiniteDistance;
// setup potential visibility set from engine // setup potential visibility set from engine
@ -3881,13 +3886,13 @@ void Bot::updateHearing () {
} }
if (distanceSq < nearestDistanceSq) { if (distanceSq < nearestDistanceSq) {
hearedEnemy = client.ent; m_hearedEnemy = client.ent;
nearestDistanceSq = distanceSq; nearestDistanceSq = distanceSq;
} }
} }
// did the bot hear someone ? // did the bot hear someone ?
if (util.isPlayer (hearedEnemy)) { if (util.isPlayer (m_hearedEnemy)) {
// change to best weapon if heard something // change to best weapon if heard something
if (m_shootTime < game.time () - 5.0f if (m_shootTime < game.time () - 5.0f
&& isOnFloor () && isOnFloor ()
@ -3909,7 +3914,7 @@ void Bot::updateHearing () {
auto getHeardOriginWithError = [&] () -> Vector { auto getHeardOriginWithError = [&] () -> Vector {
auto error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f; auto error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f;
auto origin = hearedEnemy->v.origin; auto origin = m_hearedEnemy->v.origin;
origin.x = origin.x + rg (-error, error); origin.x = origin.x + rg (-error, error);
origin.y = origin.y + rg (-error, error); origin.y = origin.y + rg (-error, error);
@ -3919,13 +3924,13 @@ 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 () || game.isNullEntity (m_lastEnemy)) { if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) {
m_lastEnemy = hearedEnemy; m_lastEnemy = m_hearedEnemy;
m_lastEnemyOrigin = getHeardOriginWithError (); m_lastEnemyOrigin = getHeardOriginWithError ();
} }
// 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 (hearedEnemy == m_lastEnemy) { if (m_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;
@ -3936,8 +3941,8 @@ void Bot::updateHearing () {
// 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 > hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) { if (distanceSq > m_hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) {
m_lastEnemy = hearedEnemy; m_lastEnemy = m_hearedEnemy;
m_lastEnemyOrigin = getHeardOriginWithError (); m_lastEnemyOrigin = getHeardOriginWithError ();
} }
else { else {
@ -3947,9 +3952,9 @@ void Bot::updateHearing () {
} }
// check if heard enemy can be seen // check if heard enemy can be seen
if (checkBodyParts (hearedEnemy)) { if (checkBodyParts (m_hearedEnemy)) {
m_enemy = hearedEnemy; m_enemy = m_hearedEnemy;
m_lastEnemy = hearedEnemy; m_lastEnemy = m_hearedEnemy;
m_lastEnemyOrigin = m_enemyOrigin; m_lastEnemyOrigin = m_enemyOrigin;
m_states |= Sense::SeeingEnemy; m_states |= Sense::SeeingEnemy;
@ -3959,15 +3964,15 @@ 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 if (cv_shoots_thru_walls
&& m_lastEnemy == hearedEnemy && m_lastEnemy == m_hearedEnemy
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct) && rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct)
&& m_seeEnemyTime + 3.0f > game.time () && m_seeEnemyTime + 3.0f > game.time ()
&& isPenetrableObstacle (hearedEnemy->v.origin)) { && isPenetrableObstacle (m_hearedEnemy->v.origin)) {
m_enemy = hearedEnemy; m_enemy = m_hearedEnemy;
m_lastEnemy = hearedEnemy; m_lastEnemy = m_hearedEnemy;
m_enemyOrigin = hearedEnemy->v.origin; m_enemyOrigin = m_hearedEnemy->v.origin;
m_lastEnemyOrigin = hearedEnemy->v.origin; m_lastEnemyOrigin = m_hearedEnemy->v.origin;
m_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy); m_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy);
m_seeEnemyTime = game.time (); m_seeEnemyTime = game.time ();

View file

@ -876,6 +876,70 @@ bool Bot::needToPauseFiring (float distance) {
return false; return false;
} }
bool Bot::checkZoom (float distance) {
int zoomMagnification = 0;
bool zoomChange = false;
// is the bot holding a sniper rifle?
if (usesSniper ()) {
// should the bot switch to the long-range zoom?
if (distance > 1500.0f) {
zoomMagnification = 2;
}
// else should the bot switch to the close-range zoom ?
else if (distance > 150.0f) {
zoomMagnification = 1;
}
// else should the bot restore the normal view ?
else if (distance <= 150.0f) {
zoomMagnification = 0;
}
}
// else is the bot holding a zoomable rifle?
else if (m_difficulty < Difficulty::Hard && usesZoomableRifle ()) {
// should the bot switch to zoomed mode?
if (distance > 800.0f) {
zoomMagnification = 1;
}
// else should the bot restore the normal view?
else if (distance <= 800.0f) {
zoomMagnification = 0;
}
}
switch (zoomMagnification) {
case 0:
if (pev->fov < 90.0f) {
zoomChange = true;
}
break;
case 1:
if (pev->fov >= 90.0f) {
zoomChange = true;
}
break;
case 2:
if (pev->fov >= 40.0f) {
zoomChange = true;
}
break;
}
if (zoomChange && m_zoomCheckTime < game.time ()) {
pev->button |= IN_ATTACK2;
m_shootTime = game.time () + 0.15f;
m_zoomCheckTime = game.time () + 0.5f;
}
return zoomChange;
}
void Bot::selectWeapons (float distance, int index, int id, int choosen) { void Bot::selectWeapons (float distance, int index, int id, int choosen) {
const auto tab = conf.getRawWeapons (); const auto tab = conf.getRawWeapons ();
@ -922,51 +986,23 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
m_shieldCheckTime = game.time () + 1.0f; m_shieldCheckTime = game.time () + 1.0f;
} }
// is the bot holding a sniper rifle? if (checkZoom (distance)) {
if (usesSniper () && m_zoomCheckTime < game.time ()) { return;
// should the bot switch to the long-range zoom?
if (distance > 1500.0f && pev->fov >= 40.0f) {
pev->button |= IN_ATTACK2;
}
// else should the bot switch to the close-range zoom ?
else if (distance > 150.0f && pev->fov >= 90.0f) {
pev->button |= IN_ATTACK2;
}
// else should the bot restore the normal view ?
else if (distance <= 150.0f && pev->fov < 90.0f) {
pev->button |= IN_ATTACK2;
}
m_zoomCheckTime = game.time () + 0.25f;
}
// else is the bot holding a zoomable rifle?
else if (m_difficulty < Difficulty::Hard && usesZoomableRifle () && m_zoomCheckTime < game.time ()) {
// should the bot switch to zoomed mode?
if (distance > 800.0f && pev->fov >= 90.0f) {
pev->button |= IN_ATTACK2;
}
// else should the bot restore the normal view?
else if (distance <= 800.0f && pev->fov < 90.0f) {
pev->button |= IN_ATTACK2;
}
m_zoomCheckTime = game.time () + 0.5f;
} }
// we're should stand still before firing sniper weapons, else sniping is useless.. // we're should stand still before firing sniper weapons, else sniping is useless..
if (usesSniper () && (m_aimFlags & (AimFlags::Enemy | AimFlags::LastEnemy)) if (usesSniper () && (m_aimFlags & (AimFlags::Enemy | AimFlags::LastEnemy))
&& !m_isReloading && pev->velocity.lengthSq () > 0.0f && !m_isReloading && pev->velocity.lengthSq () > 0.0f) {
&& getCurrentTaskId () != Task::SeekCover) {
m_moveSpeed = 0.0f; if (!cr::fzero (pev->velocity.x) || !cr::fzero (pev->velocity.y) || !cr::fzero (pev->velocity.z)) {
m_strafeSpeed = 0.0f; m_moveSpeed = 0.0f;
m_navTimeset = game.time (); m_strafeSpeed = 0.0f;
m_navTimeset = game.time ();
if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) { if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) {
m_sniperStopTime = game.time () + 2.0f; m_sniperStopTime = game.time () + 2.0f;
return; return;
}
} }
} }
@ -1331,7 +1367,7 @@ void Bot::attackMovement () {
}; };
auto strafeUpdateTime = [] () { auto strafeUpdateTime = [] () {
return game.time () + rg (0.3f, 1.0f); return game.time () + rg (0.3f, 0.8f);
}; };
// to start strafing, we have to first figure out if the target is on the left side or right side // to start strafing, we have to first figure out if the target is on the left side or right side
@ -1401,7 +1437,7 @@ void Bot::attackMovement () {
} }
} }
else if (m_fightStyle == Fight::Stay) { else if (m_fightStyle == Fight::Stay) {
const bool alreadyDucking = m_duckTime > game.time () || isDucking (); const bool alreadyDucking = m_duckTime >= game.time () || isDucking () || ((pev->button | pev->oldbuttons) & IN_DUCK);
if (alreadyDucking) { if (alreadyDucking) {
m_duckTime = game.time () + m_frameInterval * 3.0f; m_duckTime = game.time () + m_frameInterval * 3.0f;

View file

@ -1467,6 +1467,11 @@ void Bot::newRound () {
m_timeDoorOpen = 0.0f; m_timeDoorOpen = 0.0f;
for (auto &fall : m_checkFallPoint) {
fall = nullptr;
}
m_checkFall = false;
resetCollision (); resetCollision ();
resetDoubleJump (); resetDoubleJump ();

View file

@ -459,7 +459,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
} }
m_hindrance = nullptr; m_hindrance = nullptr;
float distanceSq = cr::sqrf (384.0f); float distanceSq = cr::sqrf (512.0f);
if (isOnLadder () || isInNarrowPlace ()) { if (isOnLadder () || isInNarrowPlace ()) {
return; return;
@ -489,6 +489,16 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
if (ownPrio > otherPrio) { if (ownPrio > otherPrio) {
continue; continue;
} }
// they are higher priority - make way, unless we're already making way for someone more important
if (!game.isNullEntity (m_hindrance) && m_hindrance != client.ent) {
const auto avoidPrio = bots.getPlayerPriority (m_hindrance);
// ignore because we're already avoiding someone better
if (avoidPrio > otherPrio) {
continue;
}
}
const auto nearestDistanceSq = client.ent->v.origin.distanceSq (pev->origin); const auto nearestDistanceSq = client.ent->v.origin.distanceSq (pev->origin);
if (nearestDistanceSq < cr::sqrf (pev->maxspeed) && nearestDistanceSq < distanceSq) { if (nearestDistanceSq < cr::sqrf (pev->maxspeed) && nearestDistanceSq < distanceSq) {
@ -815,6 +825,59 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
} }
} }
void Bot::checkFall () {
if (!m_checkFall) {
if (isOnFloor ()) {
m_checkFallPoint[0] = pev->origin;
if (!game.isNullEntity (m_enemy)) {
m_checkFallPoint[1] = game.getEntityOrigin (m_enemy);
}
else if (m_currentNodeIndex != kInvalidNodeIndex) {
m_checkFallPoint[1] = m_pathOrigin;
}
else {
m_checkFallPoint[1] = nullptr;
}
}
else if (!isOnLadder () && !isInWater ()) {
if (!m_checkFallPoint[0].empty () && !m_checkFallPoint[1].empty ()) {
m_checkFall = true;
}
}
}
if (!m_checkFall || !isOnFloor ()) {
return;
}
m_checkFall = false;
bool fixFall = false;
const float baseDistanceSq = m_checkFallPoint[0].distanceSq (m_checkFallPoint[1]);
const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]);
if (nowDistanceSq > baseDistanceSq
&& (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f)
&& 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) {
fixFall = true;
}
if (m_currentNodeIndex != kInvalidNodeIndex) {
if (pev->origin.distanceSq (m_checkFallPoint[1]) <= cr::sqrf (32.0f) && pev->origin.z + 16.0f < m_checkFallPoint[1].z) {
fixFall = true;
}
}
if (fixFall) {
m_currentNodeIndex = kInvalidNodeIndex;
findValidNode ();
}
}
void Bot::moveToGoal () { void Bot::moveToGoal () {
findValidNode (); findValidNode ();
@ -1641,7 +1704,7 @@ bool Bot::findNextBestNode () {
int busyIndex = kInvalidNodeIndex; int busyIndex = kInvalidNodeIndex;
float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance }; float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance };
int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex , kInvalidNodeIndex }; int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
const auto &origin = pev->origin + pev->velocity * m_frameInterval; const auto &origin = pev->origin + pev->velocity * m_frameInterval;
const auto &bucket = graph.getNodesInBucket (origin); const auto &bucket = graph.getNodesInBucket (origin);
@ -3093,9 +3156,9 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
if (needZeroVelocity && client.ent->v.velocity.length2d () > 0.0f) { if (needZeroVelocity && client.ent->v.velocity.length2d () > 0.0f) {
continue; continue;
} }
const auto length = client.origin.distanceSq (graph[index].origin); const auto distanceSq = client.origin.distanceSq (graph[index].origin);
if (length < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (90.0f), cr::sqrf (120.0f))) { if (distanceSq < cr::clamp (cr::sqrf (graph[index].radius) * 2.0f, cr::sqrf (90.0f), cr::sqrf (120.0f))) {
return true; return true;
} }
auto bot = bots[client.ent]; auto bot = bots[client.ent];
@ -3103,7 +3166,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
if (bot == nullptr || bot == this || !bot->m_isAlive) { if (bot == nullptr || bot == this || !bot->m_isAlive) {
continue; continue;
} }
return bot->m_currentNodeIndex == index; return bot->m_currentNodeIndex == index || bot->m_previousNodes[0] == index;
} }
return false; return false;
} }

View file

@ -211,7 +211,7 @@ void Bot::normal_ () {
else if (m_team == Team::CT) { else if (m_team == Team::CT) {
if (!bots.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) { if (!bots.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) {
const int index = findDefendNode (m_path->origin); const int index = findDefendNode (m_path->origin);
float campTime = rg (25.0f, 40.f); float campTime = rg (25.0f, 40.0f);
// rusher bots don't like to camp too much // rusher bots don't like to camp too much
if (m_personality == Personality::Rusher) { if (m_personality == Personality::Rusher) {
@ -270,7 +270,7 @@ void Bot::normal_ () {
if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed && mp_footsteps) if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed && mp_footsteps)
&& m_difficulty >= Difficulty::Normal && m_difficulty >= Difficulty::Normal
&& (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy)) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::HearingEnemy))
&& pev->origin.distanceSq (m_lastEnemyOrigin) < cr::sqrf (768.0f) && numEnemiesNear (pev->origin, 768.0f) >= 1
&& !isKnifeMode () && !isKnifeMode ()
&& !bots.isBombPlanted ()) { && !bots.isBombPlanted ()) {
@ -623,12 +623,20 @@ void Bot::camp_ () {
// random camp dir, or prediction // random camp dir, or prediction
auto useRandomCampDirOrPredictEnemy = [&] () { auto useRandomCampDirOrPredictEnemy = [&] () {
if (game.isNullEntity (m_lastEnemy) || !m_lastEnemyOrigin.empty ()) { if (!m_lastEnemyOrigin.empty ()) {
auto pathLength = 0; auto pathLength = m_lastPredictLength;
auto lastEnemyNearestIndex = findAimingNode (m_lastEnemyOrigin, pathLength); auto predictNode = m_lastPredictIndex;
if (pathLength > 1 && graph.exists (lastEnemyNearestIndex)) { if (pathLength > 1 && graph.exists (predictNode)) {
m_lookAtSafe = graph[lastEnemyNearestIndex].origin; m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
}
else {
pathLength = 0;
predictNode = findAimingNode (m_lastEnemyOrigin, pathLength);
if (pathLength > 1 && graph.exists (predictNode)) {
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
}
} }
} }
else { else {
@ -637,10 +645,8 @@ void Bot::camp_ () {
}; };
if (m_nextCampDirTime < game.time ()) { if (m_nextCampDirTime < game.time ()) {
m_nextCampDirTime = game.time () + rg (2.0f, 5.0f);
if (m_pathFlags & NodeFlag::Camp) { if (m_pathFlags & NodeFlag::Camp) {
Vector dest; Vector dest {};
// switch from 1 direction to the other // switch from 1 direction to the other
if (m_campDirection < 1) { if (m_campDirection < 1) {
@ -673,6 +679,7 @@ void Bot::camp_ () {
else { else {
useRandomCampDirOrPredictEnemy (); useRandomCampDirOrPredictEnemy ();
} }
m_nextCampDirTime = game.time () + rg (1.0f, 4.0f);
} }
// press remembered crouch button // press remembered crouch button
pev->button |= m_campButtons; pev->button |= m_campButtons;

View file

@ -550,10 +550,10 @@ void Bot::setAimDirection () {
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) { && pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) {
const auto nextPathIndex = m_pathWalk.next (); const auto nextPathIndex = m_pathWalk.next ();
const auto doubleNextPath = m_pathWalk.doubleNext (); const auto nextPathX2 = m_pathWalk.nextX2 ();
if (vistab.visible (m_currentNodeIndex, doubleNextPath)) { if (vistab.visible (m_currentNodeIndex, nextPathX2)) {
const auto &gn = graph[doubleNextPath]; const auto &gn = graph[nextPathX2];
m_lookAt = gn.origin + pev->view_ofs; m_lookAt = gn.origin + pev->view_ofs;
} }
else if (vistab.visible (m_currentNodeIndex, nextPathIndex)) { else if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {