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

@ -82,7 +82,7 @@ public:
int penetratePower, int penetratePower,
int maxClip, int maxClip,
int type, 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), teamAS (teamAs), buyGroup (buyGroup), buySelect (buySelect), buySelectT (buySelectT), buySelectCT (buySelectCT),
penetratePower (penetratePower), maxClip (maxClip), type (type), primaryFireHold (fireHold) penetratePower (penetratePower), maxClip (maxClip), type (type), primaryFireHold (fireHold)
{ } { }
@ -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,22 +1728,18 @@ 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 { else if (!m_isCreature
if (!m_isCreature && distanceSq2d <= reachEnemyKnifeDistanceSq
&& 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
}
} }
} }
@ -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));
@ -779,15 +778,15 @@ Vector Bot::getCustomHeight (float distance) const {
}; };
constexpr float kOffsetRanges[9][3] = { constexpr float kOffsetRanges[9][3] = {
{ 0.0f, 0.0f, 0.0f }, // none { 0.0f, 0.0f, 0.0f }, // none
{ 0.0f, 0.0f, 0.0f }, // melee { 0.0f, 0.0f, 0.0f }, // melee
{ 0.5f, -0.1f, -1.5f }, // pistol { 0.5f, -0.1f, -1.5f }, // pistol
{ 6.5f, 6.0f, -2.0f }, // shotgun { 6.5f, 6.0f, -2.0f }, // shotgun
{ 0.5f, -7.5f, -9.5f }, // zoomrifle { 0.5f, -7.5f, -9.5f }, // zoomrifle
{ 0.5f, -7.5f, -9.5f }, // rifle { 0.5f, -7.5f, -9.5f }, // rifle
{ 0.5f, -7.5f, -9.5f }, // smg { 0.5f, -7.5f, -9.5f }, // smg
{ 0.0f, -2.5f, -6.0f }, // sniper { 0.0f, -2.5f, -6.0f }, // sniper
{ 1.5f, -4.0f, -9.0f } // heavy { 1.5f, -4.0f, -9.0f } // heavy
}; };
// only high-skilled bots do that // only high-skilled bots do that
@ -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
} }
@ -1608,9 +1607,9 @@ void Bot::attackMovement () {
if (m_difficulty >= Difficulty::Normal if (m_difficulty >= Difficulty::Normal
&& distanceSq < cr::sqrf (kSprayDistance) && distanceSq < cr::sqrf (kSprayDistance)
&& (m_jumpTime + 5.0f < game.time () && (m_jumpTime + 5.0f < game.time ()
&& isOnFloor () && isOnFloor ()
&& rg (0, 1000) < (m_isReloading ? 8 : 2) && rg (0, 1000) < (m_isReloading ? 8 : 2)
&& pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) { && pev->velocity.length2d () > 150.0f) && !usesSniper () && isEnemyCone) {
pev->button |= IN_JUMP; pev->button |= IN_JUMP;
} }
@ -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,7 +877,13 @@ 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 ()) {
pev->button |= IN_JUMP; if (isInWater ()
|| !m_isCreature
|| m_lastDamageTimestamp < game.time ()
|| (m_currentTravelFlags & PathFlag::Jump)) {
pev->button |= IN_JUMP;
}
} }
break; break;
@ -894,10 +910,8 @@ 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) {
@ -905,7 +919,7 @@ void Bot::checkFall () {
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,13 +399,12 @@ 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 ();
}
} }
} }
} }
@ -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)