bot: perform a seek cover search mission to avoid infected creatures. (#707)

bot: perform a seek cover search mission to avoid infected creatures.
nav: several changes in controlling the terrain.
This commit is contained in:
commandcobra7 2025-08-06 15:44:07 +03:00 committed by GitHub
commit 286e1c8621
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 155 additions and 114 deletions

View file

@ -305,6 +305,13 @@ yb_use_engine_pvs_check "0"
//
yb_use_hitbox_enemy_targeting "0"
//
// Bots will consider glass when deciding to shoot enemies. Required for very special maps only.
// ---
// Default: "0", Min: "0", Max: "1"
//
yb_aim_trace_consider_glass "0"
//
// Binds specified key for opening bots menu.
// ---
@ -777,9 +784,9 @@ yb_random_knife_attacks "1"
//
// Maximum number for path length, to predict the enemy.
// ---
// Default: "25", Min: "15", Max: "256"
// Default: "22", Min: "15", Max: "256"
//
yb_max_nodes_for_predict "25"
yb_max_nodes_for_predict "22"
//
// Enables or disables extra hard difficulty for bots.

View file

@ -265,7 +265,7 @@ public:
void syncCollectOnline ();
void collectOnline ();
IntArray getNearestInRadius (float radius, const Vector &origin, int maxCount = -1);
IntArray getNearestInRadius (const float radius, const Vector &origin, int maxCount = -1);
public:
StringRef getAuthor () const {

View file

@ -82,7 +82,7 @@ public:
int penetratePower,
int maxClip,
int type,
bool fireHold) : id (id), name (name), model (model), price (price), minPrimaryAmmo (minPriAmmo), teamStandard (teamStd),
bool fireHold) : id (id), name (name), model (model), price (price), minPrimaryAmmo (minPriAmmo), teamStandard (teamStd),
teamAS (teamAs), buyGroup (buyGroup), buySelect (buySelect), buySelectT (buySelectT), buySelectCT (buySelectCT),
penetratePower (penetratePower), maxClip (maxClip), type (type), primaryFireHold (fireHold)
{ }
@ -244,6 +244,7 @@ private:
float m_prevTime {}; // time previously checked movement speed
float m_heavyTimestamp {}; // is it time to execute heavy-weight functions
float m_prevSpeed {}; // speed some frames before
float m_prevVelocity {}; // velocity some frames before
float m_timeDoorOpen {}; // time to next door open check
float m_timeHitDoor {}; // specific time after hitting the door
float m_lastChatTime {}; // time bot last chatted
@ -297,6 +298,7 @@ private:
float m_lastVictimTime {}; // time when bot killed an enemy
float m_killsInterval {}; // interval between kills
float m_lastDamageTimestamp {}; // last damage from take damage fn
float m_movedDistance {}; // bot moved distance
bool m_moveToGoal {}; // bot currently moving to goal??
bool m_isStuck {}; // bot is stuck
@ -318,6 +320,7 @@ private:
bool m_needToSendWelcomeChat {}; // bot needs to greet people on server?
bool m_isCreature {}; // bot is not a player, but something else ? zombie ?
bool m_isOnInfectedTeam {}; // bot is zombie (this assumes bot is a creature)
bool m_infectedEnemyTeam {}; // the enemy is a zombie (assumed to be a hostile creature)
bool m_defuseNotified {}; // bot is notified about bomb defusion
bool m_jumpSequence {}; // next path link will be jump link
bool m_checkFall {}; // check bot fall
@ -360,7 +363,6 @@ private:
Vector m_breakableOrigin {}; // origin of breakable
Vector m_rightRef {}; // right referential vector
Vector m_checkFallPoint[2] {}; // check fall point
Vector m_prevVelocity {}; // velocity some frames before
Array <edict_t *> m_ignoredBreakable {}; // list of ignored breakables
Array <edict_t *> m_ignoredItems {}; // list of pointers to entity to ignore for pickup
@ -375,7 +377,6 @@ private:
CountdownTimer m_forgetLastVictimTimer {}; // time to forget last victim position ?
CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder
CountdownTimer m_lostReachableNodeTimer {}; // bot's issuing next node, probably he's lost
CountdownTimer m_fixFallTimer {}; // timer we're fixed fall last time
CountdownTimer m_repathTimer {}; // bots is going to repath his route
private:
@ -493,7 +494,7 @@ private:
void postProcessGoals (const IntArray &goals, int result[]);
void updatePickups ();
void ensurePickupEntitiesClear ();
void checkTerrain (float movedDistance, const Vector &dirNormal);
void checkTerrain (const Vector &dirNormal);
void checkFall ();
void checkDarkness ();
void checkParachute ();

View file

@ -157,7 +157,10 @@ void Bot::checkBreakable (edict_t *touch) {
}
if (game.isNullEntity (touch)) {
m_breakableEntity = lookupBreakable ();
auto breakable = lookupBreakable ();
m_breakableEntity = breakable;
m_breakableOrigin = game.getEntityOrigin (breakable);
}
else {
if (m_breakableEntity != touch) {
@ -225,7 +228,7 @@ void Bot::checkBreakablesAround () {
// maybe time to give up?
if (m_lastBreakable == breakable && m_breakableTime + 1.5f < game.time ()) {
m_ignoredBreakable.emplace (breakable);
m_ignoredBreakable.push (breakable);
m_breakableOrigin.clear ();
m_lastBreakable = nullptr;
@ -305,7 +308,7 @@ edict_t *Bot::lookupBreakable () {
return hit;
}
m_breakableEntity = nullptr;
m_breakableOrigin = nullptr;
m_breakableOrigin.clear ();
return nullptr;
}
@ -1702,7 +1705,7 @@ void Bot::overrideConditions () {
// then start escape from bomb immediate
startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true);
}
float reachEnemyWikKnifeDistanceSq = cr::sqrf (128.0f);
float reachEnemyKnifeDistanceSq = cr::sqrf (128.0f);
// special handling, if we have a knife in our hands
if (isKnifeMode () && (util.isPlayer (m_enemy) || (cv_attack_monsters && util.isMonster (m_enemy)))) {
@ -1710,12 +1713,12 @@ void Bot::overrideConditions () {
const auto nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin);
if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex) {
reachEnemyWikKnifeDistanceSq = graph[nearestToEnemyPoint].origin.distanceSq (m_enemy->v.origin);
reachEnemyWikKnifeDistanceSq += cr::sqrf (48.0f);
reachEnemyKnifeDistanceSq = graph[nearestToEnemyPoint].origin.distanceSq (m_enemy->v.origin);
reachEnemyKnifeDistanceSq += cr::sqrf (48.0f);
}
// do nodes movement if enemy is not reachable with a knife
if (distanceSq2d > reachEnemyWikKnifeDistanceSq && (m_states & Sense::SeeingEnemy)) {
if (distanceSq2d > reachEnemyKnifeDistanceSq && (m_states & Sense::SeeingEnemy)) {
if (nearestToEnemyPoint != kInvalidNodeIndex
&& nearestToEnemyPoint != m_currentNodeIndex
&& cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) {
@ -1725,22 +1728,18 @@ void Bot::overrideConditions () {
if (tid != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) {
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true);
}
else {
if (tid == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) {
clearTask (Task::MoveToPosition);
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true);
}
else if (tid == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) {
clearTask (Task::MoveToPosition);
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true);
}
}
}
else {
if (!m_isCreature
&& distanceSq2d <= reachEnemyWikKnifeDistanceSq
&& (m_states & Sense::SeeingEnemy)
&& tid == Task::MoveToPosition) {
else if (!m_isCreature
&& distanceSq2d <= reachEnemyKnifeDistanceSq
&& (m_states & Sense::SeeingEnemy)
&& tid == Task::MoveToPosition) {
clearTask (Task::MoveToPosition); // remove any move tasks
}
clearTask (Task::MoveToPosition); // remove any move tasks
}
}
@ -1781,6 +1780,18 @@ void Bot::overrideConditions () {
m_navTimeset = game.time ();
}
}
// start searching for seek cover to avoid infected creatures
if (game.is (GameFlags::ZombieMod)
&& !m_isCreature
&& m_infectedEnemyTeam
&& util.isAlive (m_enemy)
&& m_retreatTime < game.time ()
&& pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (512.0f)) {
completeTask ();
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
}
}
void Bot::syncUpdatePredictedIndex () {
@ -2332,8 +2343,21 @@ bool Bot::isEnemyThreat () {
return false;
}
auto isOnAttackDistance = [&] (edict_t *enemy, float distance) -> bool {
if (enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (distance)) {
return true;
}
if (usesKnife ()) {
if (enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (distance)) {
return true;
}
}
return false;
};
// if enemy is near or facing us directly
if (m_enemy->v.origin.distanceSq (pev->origin) < cr::sqrf (256.0f) || (!usesKnife () && isInViewCone (m_enemy->v.origin))) {
if (isOnAttackDistance (m_enemy, 256.0f) || (!usesKnife () && isInViewCone (m_enemy->v.origin))) {
return true;
}
return false;
@ -2350,7 +2374,7 @@ bool Bot::reactOnEnemy () {
if (m_isCreature && !game.isNullEntity (m_enemy)) {
m_isEnemyReachable = false;
if (pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (118.0f)) {
if (pev->origin.distanceSq2d (m_enemy->v.origin) < cr::sqrf (128.0f)) {
m_navTimeset = game.time ();
m_isEnemyReachable = true;
}
@ -3043,7 +3067,7 @@ void Bot::update () {
m_canChooseAimDirection = true;
m_isAlive = util.isAlive (ent ());
m_team = game.getTeam (ent ());
m_healthValue = cr::clamp (pev->health, 0.0f, 100.0f);
m_healthValue = cr::clamp (pev->health, 0.0f, 99999.9f);
if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) {
m_hasC4 = !!(pev->weapons & cr::bit (Weapon::C4));
@ -3060,6 +3084,11 @@ void Bot::update () {
}
m_isCreature = isCreature ();
// damage victim action
if (m_lastDamageTimestamp < game.time () && !cr::fzero (m_lastDamageTimestamp)) {
m_lastDamageTimestamp = 0.0f;
}
// is bot movement enabled
m_botMovement = false;
@ -3262,7 +3291,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 = kMinMovedDistance; // length of different vector (distance bot moved)
m_movedDistance = kMinMovedDistance; // length of different vector (distance bot moved)
resetMovement ();
@ -3289,7 +3318,7 @@ void Bot::logic () {
// see how far bot has moved since the previous position...
if (m_checkTerrain) {
movedDistance = m_prevOrigin.distance (pev->origin);
m_movedDistance = m_prevOrigin.distance (pev->origin);
}
// save current position as previous
@ -3397,7 +3426,7 @@ void Bot::logic () {
checkBreakable (nullptr);
doPlayerAvoidance (dirNormal);
checkTerrain (movedDistance, dirNormal);
checkTerrain (dirNormal);
}
// if we have fallen from the place of move, the nearest point is allowed
@ -3443,7 +3472,7 @@ void Bot::logic () {
// save the previous speed (for checking if stuck)
m_prevSpeed = cr::abs (m_moveSpeed);
m_prevVelocity = pev->velocity;
m_prevVelocity = cr::abs (pev->velocity.length2d ());
m_lastDamageType = -1; // reset damage
}
@ -3953,7 +3982,7 @@ Vector Bot::isBombAudible () {
}
// we hear bomb if length greater than radius
if (cr::sqrf (desiredRadius) < pev->origin.distanceSq2d (bombOrigin)) {
if (pev->origin.distanceSq2d (bombOrigin) > cr::sqrf (desiredRadius)) {
return bombOrigin;
}
return nullptr;
@ -4306,9 +4335,7 @@ float Bot::getShiftSpeed () {
if (getCurrentTaskId () == Task::SeekCover
|| (m_aimFlags & AimFlags::Enemy)
|| isDucking ()
|| m_isCreature
|| (pev->button & IN_DUCK)
|| (m_oldButtons & IN_DUCK)
|| ((pev->button | pev->oldbuttons) & IN_DUCK)
|| (m_currentTravelFlags & PathFlag::Jump)
|| (m_pathFlags & NodeFlag::Ladder)
|| isOnLadder ()
@ -4337,9 +4364,10 @@ void Bot::refreshCreatureStatus (char *infobuffer) {
// if bot is on infected team, and zombie mode is active, assume bot is a creature/zombie
m_isOnInfectedTeam = game.getRealTeam (ent ()) == infectedTeam;
m_infectedEnemyTeam = game.getRealTeam (m_enemy) == infectedTeam;
// do not process next if already infected
if (m_isOnInfectedTeam) {
if (m_isOnInfectedTeam || m_infectedEnemyTeam) {
return;
}
}

View file

@ -669,8 +669,7 @@ Vector Bot::getBodyOffsetError (float distance) {
m_aimLastError = Vector (
rg (mins.x * hitError, maxs.x * hitError),
rg (mins.y * hitError, maxs.y * hitError),
rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f)
);
rg (mins.z * hitError * 0.5f, maxs.z * hitError * 0.5f));
const auto &aimError = conf.getDifficultyTweaks (m_difficulty)->aimError;
m_aimLastError += Vector (rg (-aimError.x, aimError.x), rg (-aimError.y, aimError.y), rg (-aimError.z, aimError.z));
@ -779,15 +778,15 @@ Vector Bot::getCustomHeight (float distance) const {
};
constexpr float kOffsetRanges[9][3] = {
{ 0.0f, 0.0f, 0.0f }, // none
{ 0.0f, 0.0f, 0.0f }, // melee
{ 0.5f, -0.1f, -1.5f }, // pistol
{ 6.5f, 6.0f, -2.0f }, // shotgun
{ 0.5f, -7.5f, -9.5f }, // zoomrifle
{ 0.5f, -7.5f, -9.5f }, // rifle
{ 0.5f, -7.5f, -9.5f }, // smg
{ 0.0f, -2.5f, -6.0f }, // sniper
{ 1.5f, -4.0f, -9.0f } // heavy
{ 0.0f, 0.0f, 0.0f }, // none
{ 0.0f, 0.0f, 0.0f }, // melee
{ 0.5f, -0.1f, -1.5f }, // pistol
{ 6.5f, 6.0f, -2.0f }, // shotgun
{ 0.5f, -7.5f, -9.5f }, // zoomrifle
{ 0.5f, -7.5f, -9.5f }, // rifle
{ 0.5f, -7.5f, -9.5f }, // smg
{ 0.0f, -2.5f, -6.0f }, // sniper
{ 1.5f, -4.0f, -9.0f } // heavy
};
// only high-skilled bots do that
@ -815,7 +814,7 @@ bool Bot::isFriendInLineOfFire (float distance) const {
}
TraceResult tr {};
game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle.normalize (), TraceIgnore::None, ent (), &tr);
game.testLine (getEyesPos (), getEyesPos () + pev->v_angle.normalize_apx () * distance, TraceIgnore::None, ent (), &tr);
// check if we hit something
if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) {
@ -1125,7 +1124,7 @@ void Bot::selectWeapons (float distance, int, int id, int choosen) {
else if (isShieldDrawn ()
|| m_isReloading
|| (hasEnemy && (m_enemy->v.button & IN_RELOAD))
|| (hasEnemy && !seesEntity (m_enemy->v.origin))) {
|| (!hasEnemy && !seesEntity (m_enemy->v.origin))) {
pev->button |= IN_ATTACK2; // draw out the shield
}
@ -1608,9 +1607,9 @@ void Bot::attackMovement () {
if (m_difficulty >= Difficulty::Normal
&& distanceSq < cr::sqrf (kSprayDistance)
&& (m_jumpTime + 5.0f < game.time ()
&& isOnFloor ()
&& rg (0, 1000) < (m_isReloading ? 8 : 2)
&& pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) {
&& isOnFloor ()
&& rg (0, 1000) < (m_isReloading ? 8 : 2)
&& pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) {
pev->button |= IN_JUMP;
}
@ -1814,14 +1813,13 @@ bool Bot::hasAnyAmmoInClip () {
}
bool Bot::isKnifeMode () {
return cv_jasonmode ||
(usesKnife () && !hasAnyWeapons ())
return cv_jasonmode || (usesKnife () && !hasAnyWeapons ())
|| m_isCreature
|| ((m_states & Sense::SeeingEnemy) && usesKnife () && !hasAnyAmmoInClip ());
}
bool Bot::isGrenadeWar () {
const bool hasSomeGreandes = bestGrenadeCarried () != -1;
const bool hasSomeGreandes = bestGrenadeCarried () != kGrenadeInventoryEmpty;
// if has grenade an not other weapons, assume we're in grenade war
if (!hasAnyWeapons () && hasSomeGreandes) {
@ -2113,10 +2111,10 @@ void Bot::checkReload () {
}
float Bot::calculateScaleFactor (edict_t *ent) const {
Vector entSize = ent->v.maxs - ent->v.mins;
const auto &entSize = ent->v.maxs - ent->v.mins;
const float entArea = 2.0f * (entSize.x * entSize.y + entSize.y * entSize.z + entSize.x * entSize.z);
Vector botSize = pev->maxs - pev->mins;
const auto &botSize = pev->maxs - pev->mins;
const float botArea = 2.0f * (botSize.x * botSize.y + botSize.y * botSize.z + botSize.x * botSize.z);
return entArea / botArea;

View file

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

View file

@ -1518,6 +1518,7 @@ void Bot::newRound () {
m_askCheckTime = rg (30.0f, 90.0f);
m_minSpeed = 260.0f;
m_prevSpeed = 0.0f;
m_prevVelocity = 0.0f;
m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance);
m_prevTime = game.time ();
m_lookUpdateTime = game.time ();
@ -1602,7 +1603,6 @@ void Bot::newRound () {
m_approachingLadderTimer.invalidate ();
m_forgetLastVictimTimer.invalidate ();
m_lostReachableNodeTimer.invalidate ();
m_fixFallTimer.invalidate ();
m_repathTimer.invalidate ();
for (auto &timer : m_chatterTimes) {

View file

@ -173,7 +173,10 @@ int Bot::findBestGoal () {
float campDesire = rg (0.0f, 100.0f) + defensive;
if (!usesCampGun ()) {
campDesire *= 0.5f;
campDesire = 0.0f;
}
else if (usesSniper ()) {
campDesire = rg (1.5f, 2.5f) * campDesire;
}
int tactic = GoalTactic::Defensive;
@ -285,14 +288,14 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
else if (tactic == GoalTactic::Goal && !graph.m_goalPoints.empty ()) { // map goal node
// force bomber to select closest goal, if round-start goal was reset by something
if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ()) {
if (m_isVIP || (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ())) {
float nearestDistanceSq = kInfiniteDistance;
int count = 0;
for (const auto &point : graph.m_goalPoints) {
const float distanceSq = graph[point].origin.distanceSq (pev->origin);
if (distanceSq > cr::sqrf (1024.0f)) {
if (distanceSq > cr::sqrf (1024.0f) || isGroupOfEnemies (graph[point].origin)) {
continue;
}
if (distanceSq < nearestDistanceSq) {
@ -589,7 +592,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
}
}
void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
void Bot::checkTerrain (const Vector &dirNormal) {
// if avoiding someone do not consider stuck
TraceResult tr {};
@ -599,6 +602,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
// minimal speed for consider stuck
const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4;
const auto randomProbeTime = rg (0.75f, 1.15f);
// standing still, no need to check?
if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed)
@ -612,7 +616,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
m_firstCollideTime = 0.0f;
}
// didn't we move enough previously?
else if (movedDistance < kMinMovedDistance && m_prevSpeed > 20.0f) {
else if (m_movedDistance < kMinMovedDistance && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) {
m_prevTime = game.time (); // then consider being stuck
m_isStuck = true;
@ -642,9 +646,16 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
}
}
if (m_probeTime >= game.time ()) {
m_isStuck = true;
}
else if (m_probeTime + randomProbeTime < game.time () && !cr::fzero (m_probeTime)) {
resetCollision (); // resets its collision state because it was too long time in probing state
}
// not stuck?
if (!m_isStuck) {
if (m_probeTime + rg (0.75f, 1.15f) < game.time ()) {
if (m_probeTime + randomProbeTime < game.time ()) {
resetCollision (); // reset collision memory if not being stuck for 0.5 secs
}
else {
@ -845,7 +856,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
}
m_collideTime = game.time ();
m_probeTime = game.time () + 0.5f;
m_probeTime = game.time () + randomProbeTime;
m_collisionProbeBits = bits;
m_collisionState = CollisionState::Probing;
m_collStateIndex = 0;
@ -855,10 +866,9 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
if (m_collisionState == CollisionState::Probing) {
if (m_probeTime < game.time ()) {
m_collStateIndex++;
m_probeTime = game.time () + 0.5f;
m_probeTime = game.time () + randomProbeTime;
if (m_collStateIndex >= kMaxCollideMoves) {
m_navTimeset = game.time () - 5.0f;
resetCollision ();
}
}
@ -867,7 +877,13 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
switch (m_collideMoves[m_collStateIndex]) {
case CollisionState::Jump:
if ((isOnFloor () || isInWater ()) && !isOnLadder ()) {
pev->button |= IN_JUMP;
if (isInWater ()
|| !m_isCreature
|| m_lastDamageTimestamp < game.time ()
|| (m_currentTravelFlags & PathFlag::Jump)) {
pev->button |= IN_JUMP;
}
}
break;
@ -894,10 +910,8 @@ void Bot::checkFall () {
if (isPreviousLadder ()) {
return;
}
else if (graph.exists (m_currentNodeIndex)) {
if (graph[m_currentNodeIndex].flags & NodeFlag::Ladder) {
return;
}
else if ((m_pathFlags & NodeFlag::Ladder) && isPreviousLadder () && isOnLadder ()) {
return;
}
if (!m_checkFall) {
@ -905,7 +919,7 @@ void Bot::checkFall () {
m_checkFallPoint[0] = pev->origin;
if (!game.isNullEntity (m_enemy)) {
m_checkFallPoint[1] = game.getEntityOrigin (m_enemy);
m_checkFallPoint[1] = m_enemy->v.origin;
}
else if (m_currentNodeIndex != kInvalidNodeIndex) {
m_checkFallPoint[1] = m_pathOrigin;
@ -921,7 +935,7 @@ void Bot::checkFall () {
}
}
if (!m_checkFall || !isOnFloor () || !m_fixFallTimer.elapsed ()) {
if (!m_checkFall || !isOnFloor ()) {
return;
}
m_checkFall = false;
@ -931,25 +945,23 @@ void Bot::checkFall () {
const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]);
if (nowDistanceSq > baseDistanceSq
&& (nowDistanceSq > baseDistanceSq * 1.8f || nowDistanceSq > baseDistanceSq + 260.0f)
&& baseDistanceSq >= cr::sqrf (124.0f) && nowDistanceSq >= cr::sqrf (146.0f)) {
&& (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f)
&& baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
fixFall = true;
}
else if (cr::abs (m_checkFallPoint[1].z) > cr::abs (pev->origin.z) + 138.0f
|| cr::abs (m_checkFallPoint[0].z) > cr::abs (pev->origin.z) + 138.0f) {
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)
&& cr::abs (m_checkFallPoint[1].z) > cr::abs (pev->origin.z) + 72.0f) {
&& nowDistanceSq > cr::sqrf (16.0f)
&& m_checkFallPoint[1].z > pev->origin.z + 62.0f) {
fixFall = true;
}
if (fixFall) {
m_currentNodeIndex = kInvalidNodeIndex;
findValidNode ();
m_fixFallTimer.start (1.0f);
}
}
@ -1295,8 +1307,8 @@ bool Bot::updateNavigation () {
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 ()) {
if (distanceSq < cr::sqrf (96.0f)) {
if ((dirToPoint | forwardMove) < 0.0f) {
m_moveSpeed = -pev->maxspeed;
}
}
@ -1308,7 +1320,7 @@ bool Bot::updateNavigation () {
}
float desiredDistanceSq = cr::sqrf (8.0f);
float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin);
const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin);
// initialize the radius for a special node type, where the node is considered to be reached
if (m_pathFlags & NodeFlag::Lift) {
@ -1948,8 +1960,7 @@ bool Bot::findNextBestNodeEx (const IntArray &data, bool handleFails) {
float Bot::getEstimatedNodeReachTime () {
const bool longTermReachability = (m_pathFlags & NodeFlag::Crouch)
|| (m_pathFlags & NodeFlag::Ladder)
|| (pev->button & IN_DUCK)
|| (m_oldButtons & IN_DUCK);
|| ((pev->button | pev->oldbuttons) & IN_DUCK);
float estimatedTime = longTermReachability ? 8.5f : 3.5f;
@ -1958,6 +1969,10 @@ float Bot::getEstimatedNodeReachTime () {
return estimatedTime;
}
if (m_lastDamageTimestamp < game.time () && !cr::fzero (m_lastDamageTimestamp) && !m_isStuck && m_isCreature) {
return estimatedTime;
}
// calculate 'real' time that we need to get from one node to another
if (graph.exists (m_currentNodeIndex) && graph.exists (m_previousNodes[0])) {
const float distanceSq = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin);
@ -3097,7 +3112,7 @@ bool Bot::isBlockedLeft () {
pev->angles.angleVectors (&forward, &right, nullptr);
// do a trace to the left...
game.testLine (pev->origin, forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr);
game.testLine (pev->origin, pev->origin - forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !util.isDoorEntity (tr.pHit)) {
@ -3110,8 +3125,8 @@ bool Bot::isBlockedRight () {
TraceResult tr {};
float direction = 48.0f;
if (m_moveSpeed < 0.0f) {
direction = -48.0f;
if (m_moveSpeed > 0.0f) {
direction = 48.0f;
}
Vector right {}, forward {};
pev->angles.angleVectors (&forward, &right, nullptr);

View file

@ -121,7 +121,7 @@ void Bot::normal_ () {
if (allowedCampWeapon && m_timeCamping + 10.0f < game.time () && !m_hasHostage) {
bool campingAllowed = true;
// Check if it's not allowed for this team to camp here
// check if it's not allowed for this team to camp here
if (m_team == Team::Terrorist) {
if (m_pathFlags & NodeFlag::CTOnly) {
campingAllowed = false;
@ -134,27 +134,20 @@ void Bot::normal_ () {
}
// don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp
if (campingAllowed
&& (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4))) {
if (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4)) {
campingAllowed = false;
}
// check if another bot is already camping here
if (campingAllowed && isOccupiedNode (m_currentNodeIndex)) {
if (isOccupiedNode (m_currentNodeIndex)) {
campingAllowed = false;
}
// skip sniper node if we don't have sniper weapon
if (campingAllowed && !usesSniper () && (m_pathFlags & NodeFlag::Sniper)) {
if (!usesSniper () && (m_pathFlags & NodeFlag::Sniper)) {
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) {
@ -406,13 +399,12 @@ void Bot::huntEnemy_ () {
}
// bots skill higher than 60?
if (cv_walking_allowed && mp_footsteps && m_difficulty >= Difficulty::Normal && !isKnifeMode ()) {
if (cv_walking_allowed && mp_footsteps && m_difficulty >= Difficulty::Normal) {
// then make him move slow if near enemy
if (!(m_currentTravelFlags & PathFlag::Jump)) {
if (m_currentNodeIndex != kInvalidNodeIndex) {
if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time ()) {
m_moveSpeed = getShiftSpeed ();
}
if (m_currentNodeIndex != kInvalidNodeIndex && !(m_currentTravelFlags & PathFlag::Jump)) {
if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time ()) {
m_moveSpeed = getShiftSpeed ();
}
}
}
@ -623,7 +615,7 @@ void Bot::blind_ () {
}
void Bot::camp_ () {
if (!cv_camping_allowed || m_isCreature) {
if (!cv_camping_allowed || isKnifeMode ()) {
completeTask ();
return;
}
@ -1480,7 +1472,7 @@ void Bot::shootBreakable_ () {
m_ignoredBreakable.push (tr.pHit);
m_breakableEntity = nullptr;
m_breakableOrigin = nullptr;
m_breakableOrigin.clear ();
completeTask ();
return;
@ -1514,7 +1506,7 @@ void Bot::shootBreakable_ () {
// if with knife with no ammo, recompute breakable distance
if (!hasAnyAmmoInClip ()
&& usesKnife ()
&& distToObstacle > cr::sqrf (72.0f)) {
&& distToObstacle > cr::sqrf (32.0f)) {
completeTask ();
}

View file

@ -433,7 +433,7 @@ void Bot::setAimDirection () {
}
// don't switch view right away after loosing focus with current enemy
if ((m_shootTime + 1.5f > game.time () || m_seeEnemyTime + 1.5f > game.time ())
if (m_seeEnemyTime + 1.5f > game.time ()
&& m_forgetLastVictimTimer.elapsed ()
&& !m_lastEnemyOrigin.empty ()
&& util.isAlive (m_lastEnemy)