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" 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. // 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. // 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. // Enables or disables extra hard difficulty for bots.

View file

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

View file

@ -244,6 +244,7 @@ private:
float m_prevTime {}; // time previously checked movement speed float m_prevTime {}; // time previously checked movement speed
float m_heavyTimestamp {}; // is it time to execute heavy-weight functions float m_heavyTimestamp {}; // is it time to execute heavy-weight functions
float m_prevSpeed {}; // speed some frames before 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_timeDoorOpen {}; // time to next door open check
float m_timeHitDoor {}; // specific time after hitting the door float m_timeHitDoor {}; // specific time after hitting the door
float m_lastChatTime {}; // time bot last chatted float m_lastChatTime {}; // time bot last chatted
@ -297,6 +298,7 @@ private:
float m_lastVictimTime {}; // time when bot killed an enemy float m_lastVictimTime {}; // time when bot killed an enemy
float m_killsInterval {}; // interval between kills float m_killsInterval {}; // interval between kills
float m_lastDamageTimestamp {}; // last damage from take damage fn 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_moveToGoal {}; // bot currently moving to goal??
bool m_isStuck {}; // bot is stuck bool m_isStuck {}; // bot is stuck
@ -318,6 +320,7 @@ private:
bool m_needToSendWelcomeChat {}; // bot needs to greet people on server? bool m_needToSendWelcomeChat {}; // bot needs to greet people on server?
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_isOnInfectedTeam {}; // bot is zombie (this assumes bot is a creature) 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_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 bool m_checkFall {}; // check bot fall
@ -360,7 +363,6 @@ private:
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 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_ignoredBreakable {}; // list of ignored breakables
Array <edict_t *> m_ignoredItems {}; // list of pointers to entity to ignore for pickup 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_forgetLastVictimTimer {}; // time to forget last victim position ?
CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder
CountdownTimer m_lostReachableNodeTimer {}; // bot's issuing next node, probably he's lost 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 CountdownTimer m_repathTimer {}; // bots is going to repath his route
private: private:
@ -493,7 +494,7 @@ private:
void postProcessGoals (const IntArray &goals, int result[]); void postProcessGoals (const IntArray &goals, int result[]);
void updatePickups (); void updatePickups ();
void ensurePickupEntitiesClear (); void ensurePickupEntitiesClear ();
void checkTerrain (float movedDistance, const Vector &dirNormal); void checkTerrain (const Vector &dirNormal);
void checkFall (); void checkFall ();
void checkDarkness (); void checkDarkness ();
void checkParachute (); void checkParachute ();

View file

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

View file

@ -669,8 +669,7 @@ Vector Bot::getBodyOffsetError (float distance) {
m_aimLastError = Vector ( m_aimLastError = Vector (
rg (mins.x * hitError, maxs.x * hitError), rg (mins.x * hitError, maxs.x * hitError),
rg (mins.y * hitError, maxs.y * 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; 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)); m_aimLastError += Vector (rg (-aimError.x, aimError.x), rg (-aimError.y, aimError.y), rg (-aimError.z, aimError.z));
@ -815,7 +814,7 @@ bool Bot::isFriendInLineOfFire (float distance) const {
} }
TraceResult tr {}; 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 // check if we hit something
if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) { 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 () else if (isShieldDrawn ()
|| m_isReloading || m_isReloading
|| (hasEnemy && (m_enemy->v.button & IN_RELOAD)) || (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 pev->button |= IN_ATTACK2; // draw out the shield
} }
@ -1814,14 +1813,13 @@ bool Bot::hasAnyAmmoInClip () {
} }
bool Bot::isKnifeMode () { bool Bot::isKnifeMode () {
return cv_jasonmode || return cv_jasonmode || (usesKnife () && !hasAnyWeapons ())
(usesKnife () && !hasAnyWeapons ())
|| m_isCreature || m_isCreature
|| ((m_states & Sense::SeeingEnemy) && usesKnife () && !hasAnyAmmoInClip ()); || ((m_states & Sense::SeeingEnemy) && usesKnife () && !hasAnyAmmoInClip ());
} }
bool Bot::isGrenadeWar () { 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 has grenade an not other weapons, assume we're in grenade war
if (!hasAnyWeapons () && hasSomeGreandes) { if (!hasAnyWeapons () && hasSomeGreandes) {
@ -2113,10 +2111,10 @@ void Bot::checkReload () {
} }
float Bot::calculateScaleFactor (edict_t *ent) const { 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); 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); const float botArea = 2.0f * (botSize.x * botSize.y + botSize.y * botSize.z + botSize.x * botSize.z);
return entArea / botArea; return entArea / botArea;

View file

@ -566,7 +566,7 @@ int BotGraph::getNearest (const Vector &origin, const float range, int flags) {
return index; 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 // returns all nodes within radius from position
const float radiusSq = cr::sqrf (radius); const float radiusSq = cr::sqrf (radius);

View file

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

View file

@ -173,7 +173,10 @@ int Bot::findBestGoal () {
float campDesire = rg (0.0f, 100.0f) + defensive; float campDesire = rg (0.0f, 100.0f) + defensive;
if (!usesCampGun ()) { if (!usesCampGun ()) {
campDesire *= 0.5f; campDesire = 0.0f;
}
else if (usesSniper ()) {
campDesire = rg (1.5f, 2.5f) * campDesire;
} }
int tactic = GoalTactic::Defensive; 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 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 // 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; float nearestDistanceSq = kInfiniteDistance;
int count = 0; int count = 0;
for (const auto &point : graph.m_goalPoints) { for (const auto &point : graph.m_goalPoints) {
const float distanceSq = graph[point].origin.distanceSq (pev->origin); 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; continue;
} }
if (distanceSq < nearestDistanceSq) { 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 // if avoiding someone do not consider stuck
TraceResult tr {}; TraceResult tr {};
@ -599,6 +602,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
// minimal speed for consider stuck // minimal speed for consider stuck
const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4; const float minimalSpeed = isDucking () ? kMinMovedDistance : kMinMovedDistance * 4;
const auto randomProbeTime = rg (0.75f, 1.15f);
// standing still, no need to check? // standing still, no need to check?
if ((cr::abs (m_moveSpeed) >= minimalSpeed || cr::abs (m_strafeSpeed) >= minimalSpeed) 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; m_firstCollideTime = 0.0f;
} }
// didn't we move enough previously? // 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_prevTime = game.time (); // then consider being stuck
m_isStuck = true; 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? // not stuck?
if (!m_isStuck) { 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 resetCollision (); // reset collision memory if not being stuck for 0.5 secs
} }
else { else {
@ -845,7 +856,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
} }
m_collideTime = game.time (); m_collideTime = game.time ();
m_probeTime = game.time () + 0.5f; m_probeTime = game.time () + randomProbeTime;
m_collisionProbeBits = bits; m_collisionProbeBits = bits;
m_collisionState = CollisionState::Probing; m_collisionState = CollisionState::Probing;
m_collStateIndex = 0; m_collStateIndex = 0;
@ -855,10 +866,9 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
if (m_collisionState == CollisionState::Probing) { if (m_collisionState == CollisionState::Probing) {
if (m_probeTime < game.time ()) { if (m_probeTime < game.time ()) {
m_collStateIndex++; m_collStateIndex++;
m_probeTime = game.time () + 0.5f; m_probeTime = game.time () + randomProbeTime;
if (m_collStateIndex >= kMaxCollideMoves) { if (m_collStateIndex >= kMaxCollideMoves) {
m_navTimeset = game.time () - 5.0f;
resetCollision (); resetCollision ();
} }
} }
@ -867,8 +877,14 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
switch (m_collideMoves[m_collStateIndex]) { switch (m_collideMoves[m_collStateIndex]) {
case CollisionState::Jump: case CollisionState::Jump:
if ((isOnFloor () || isInWater ()) && !isOnLadder ()) { if ((isOnFloor () || isInWater ()) && !isOnLadder ()) {
if (isInWater ()
|| !m_isCreature
|| m_lastDamageTimestamp < game.time ()
|| (m_currentTravelFlags & PathFlag::Jump)) {
pev->button |= IN_JUMP; pev->button |= IN_JUMP;
} }
}
break; break;
case CollisionState::Duck: case CollisionState::Duck:
@ -894,18 +910,16 @@ void Bot::checkFall () {
if (isPreviousLadder ()) { if (isPreviousLadder ()) {
return; return;
} }
else if (graph.exists (m_currentNodeIndex)) { else if ((m_pathFlags & NodeFlag::Ladder) && isPreviousLadder () && isOnLadder ()) {
if (graph[m_currentNodeIndex].flags & NodeFlag::Ladder) {
return; return;
} }
}
if (!m_checkFall) { if (!m_checkFall) {
if (isOnFloor ()) { if (isOnFloor ()) {
m_checkFallPoint[0] = pev->origin; m_checkFallPoint[0] = pev->origin;
if (!game.isNullEntity (m_enemy)) { if (!game.isNullEntity (m_enemy)) {
m_checkFallPoint[1] = game.getEntityOrigin (m_enemy); m_checkFallPoint[1] = m_enemy->v.origin;
} }
else if (m_currentNodeIndex != kInvalidNodeIndex) { else if (m_currentNodeIndex != kInvalidNodeIndex) {
m_checkFallPoint[1] = m_pathOrigin; m_checkFallPoint[1] = m_pathOrigin;
@ -921,7 +935,7 @@ void Bot::checkFall () {
} }
} }
if (!m_checkFall || !isOnFloor () || !m_fixFallTimer.elapsed ()) { if (!m_checkFall || !isOnFloor ()) {
return; return;
} }
m_checkFall = false; m_checkFall = false;
@ -931,25 +945,23 @@ void Bot::checkFall () {
const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]); const float nowDistanceSq = pev->origin.distanceSq (m_checkFallPoint[1]);
if (nowDistanceSq > baseDistanceSq if (nowDistanceSq > baseDistanceSq
&& (nowDistanceSq > baseDistanceSq * 1.8f || nowDistanceSq > baseDistanceSq + 260.0f) && (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f)
&& baseDistanceSq >= cr::sqrf (124.0f) && nowDistanceSq >= cr::sqrf (146.0f)) { && baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
fixFall = true; fixFall = true;
} }
else if (cr::abs (m_checkFallPoint[1].z) > cr::abs (pev->origin.z) + 138.0f else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f
|| cr::abs (m_checkFallPoint[0].z) > cr::abs (pev->origin.z) + 138.0f) { && m_checkFallPoint[0].z > pev->origin.z + 128.0f) {
fixFall = true; fixFall = true;
} }
else if (m_currentNodeIndex != kInvalidNodeIndex else if (m_currentNodeIndex != kInvalidNodeIndex
&& nowDistanceSq > cr::sqrf (32.0f) && nowDistanceSq > cr::sqrf (16.0f)
&& cr::abs (m_checkFallPoint[1].z) > cr::abs (pev->origin.z) + 72.0f) { && m_checkFallPoint[1].z > pev->origin.z + 62.0f) {
fixFall = true; fixFall = true;
} }
if (fixFall) { if (fixFall) {
m_currentNodeIndex = kInvalidNodeIndex; m_currentNodeIndex = kInvalidNodeIndex;
findValidNode (); findValidNode ();
m_fixFallTimer.start (1.0f);
} }
} }
@ -1295,8 +1307,8 @@ bool Bot::updateNavigation () {
const auto &dirToPoint = (pev->origin - origin).normalize2d_apx (); const auto &dirToPoint = (pev->origin - origin).normalize2d_apx ();
const auto &forwardMove = m_moveAngles.forward ().normalize2d_apx (); const auto &forwardMove = m_moveAngles.forward ().normalize2d_apx ();
if (distanceSq < cr::sqrf (80.0f)) { if (distanceSq < cr::sqrf (96.0f)) {
if ((dirToPoint | forwardMove) < 0.0f && !checkWallOnBehind ()) { if ((dirToPoint | forwardMove) < 0.0f) {
m_moveSpeed = -pev->maxspeed; m_moveSpeed = -pev->maxspeed;
} }
} }
@ -1308,7 +1320,7 @@ bool Bot::updateNavigation () {
} }
float desiredDistanceSq = cr::sqrf (8.0f); 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 // initialize the radius for a special node type, where the node is considered to be reached
if (m_pathFlags & NodeFlag::Lift) { if (m_pathFlags & NodeFlag::Lift) {
@ -1948,8 +1960,7 @@ bool Bot::findNextBestNodeEx (const IntArray &data, bool handleFails) {
float Bot::getEstimatedNodeReachTime () { float Bot::getEstimatedNodeReachTime () {
const bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) const bool longTermReachability = (m_pathFlags & NodeFlag::Crouch)
|| (m_pathFlags & NodeFlag::Ladder) || (m_pathFlags & NodeFlag::Ladder)
|| (pev->button & IN_DUCK) || ((pev->button | pev->oldbuttons) & IN_DUCK);
|| (m_oldButtons & IN_DUCK);
float estimatedTime = longTermReachability ? 8.5f : 3.5f; float estimatedTime = longTermReachability ? 8.5f : 3.5f;
@ -1958,6 +1969,10 @@ float Bot::getEstimatedNodeReachTime () {
return estimatedTime; 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 // calculate 'real' time that we need to get from one node to another
if (graph.exists (m_currentNodeIndex) && graph.exists (m_previousNodes[0])) { if (graph.exists (m_currentNodeIndex) && graph.exists (m_previousNodes[0])) {
const float distanceSq = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin); 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); pev->angles.angleVectors (&forward, &right, nullptr);
// do a trace to the left... // 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... // check if the trace hit something...
if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !util.isDoorEntity (tr.pHit)) { if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && !util.isDoorEntity (tr.pHit)) {
@ -3110,8 +3125,8 @@ bool Bot::isBlockedRight () {
TraceResult tr {}; TraceResult tr {};
float direction = 48.0f; float direction = 48.0f;
if (m_moveSpeed < 0.0f) { if (m_moveSpeed > 0.0f) {
direction = -48.0f; direction = 48.0f;
} }
Vector right {}, forward {}; Vector right {}, forward {};
pev->angles.angleVectors (&forward, &right, nullptr); 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) { if (allowedCampWeapon && m_timeCamping + 10.0f < game.time () && !m_hasHostage) {
bool campingAllowed = true; 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_team == Team::Terrorist) {
if (m_pathFlags & NodeFlag::CTOnly) { if (m_pathFlags & NodeFlag::CTOnly) {
campingAllowed = false; 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 // don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp
if (campingAllowed if (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4)) {
&& (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4))) {
campingAllowed = false; campingAllowed = false;
} }
// check if another bot is already camping here // check if another bot is already camping here
if (campingAllowed && isOccupiedNode (m_currentNodeIndex)) { if (isOccupiedNode (m_currentNodeIndex)) {
campingAllowed = false; campingAllowed = false;
} }
// skip sniper node if we don't have sniper weapon // 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; 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) { if (campingAllowed) {
// crouched camping here? // crouched camping here?
if (m_pathFlags & NodeFlag::Crouch) { if (m_pathFlags & NodeFlag::Crouch) {
@ -406,17 +399,16 @@ void Bot::huntEnemy_ () {
} }
// bots skill higher than 60? // 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 // then make him move slow if near enemy
if (!(m_currentTravelFlags & PathFlag::Jump)) { if (m_currentNodeIndex != kInvalidNodeIndex && !(m_currentTravelFlags & PathFlag::Jump)) {
if (m_currentNodeIndex != kInvalidNodeIndex) {
if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time ()) { if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time ()) {
m_moveSpeed = getShiftSpeed (); m_moveSpeed = getShiftSpeed ();
} }
} }
} }
} }
}
void Bot::seekCover_ () { void Bot::seekCover_ () {
m_aimFlags |= AimFlags::Nav; m_aimFlags |= AimFlags::Nav;
@ -623,7 +615,7 @@ void Bot::blind_ () {
} }
void Bot::camp_ () { void Bot::camp_ () {
if (!cv_camping_allowed || m_isCreature) { if (!cv_camping_allowed || isKnifeMode ()) {
completeTask (); completeTask ();
return; return;
} }
@ -1480,7 +1472,7 @@ void Bot::shootBreakable_ () {
m_ignoredBreakable.push (tr.pHit); m_ignoredBreakable.push (tr.pHit);
m_breakableEntity = nullptr; m_breakableEntity = nullptr;
m_breakableOrigin = nullptr; m_breakableOrigin.clear ();
completeTask (); completeTask ();
return; return;
@ -1514,7 +1506,7 @@ void Bot::shootBreakable_ () {
// if with knife with no ammo, recompute breakable distance // if with knife with no ammo, recompute breakable distance
if (!hasAnyAmmoInClip () if (!hasAnyAmmoInClip ()
&& usesKnife () && usesKnife ()
&& distToObstacle > cr::sqrf (72.0f)) { && distToObstacle > cr::sqrf (32.0f)) {
completeTask (); completeTask ();
} }

View file

@ -433,7 +433,7 @@ void Bot::setAimDirection () {
} }
// don't switch view right away after loosing focus with current enemy // 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_forgetLastVictimTimer.elapsed ()
&& !m_lastEnemyOrigin.empty () && !m_lastEnemyOrigin.empty ()
&& util.isAlive (m_lastEnemy) && util.isAlive (m_lastEnemy)