nav: fall controls are not good. (#656)
nav: fall controls are not good. nav: increased behavior rate in finding the best goal. nav: avoiding the door after hitting it. combat: if the enemy is using a sniper rifle, move. combat: enemy group functionality has been improved. combat: if we are doing a camping task, do not switch to a knife at close range. aim: slight changes to enemy prediction. --------- Co-authored-by: jeefo <dmitry@jeefo.net>
This commit is contained in:
parent
6152a2d2ce
commit
e717710bd1
9 changed files with 245 additions and 211 deletions
|
|
@ -128,7 +128,7 @@ public:
|
|||
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);
|
||||
bool balancedKickRandom (bool decQuota);
|
||||
bool hasCustomCSDMSpawnEntities ();
|
||||
bool isLineBlockedBySmoke (const Vector &from, const Vector &to, float grenadeBloat = 1.0f);
|
||||
bool isLineBlockedBySmoke (const Vector &from, const Vector &to);
|
||||
bool isFrameSkipDisabled ();
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ private:
|
|||
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
|
||||
float m_timeLogoSpray {}; // time bot last spray logo
|
||||
float m_knifeAttackTime {}; // time to rush with knife (at the beginning of the round)
|
||||
|
|
@ -284,7 +285,6 @@ private:
|
|||
float m_playServerTime {}; // time bot spent in the game
|
||||
float m_changeViewTime {}; // timestamp to change look at while at freezetime
|
||||
float m_breakableTime {}; // breakable acquired time
|
||||
float m_stuckTimestamp {}; // last time was stuck
|
||||
float m_timeDebugUpdateTime {}; // time to update last debug timestamp
|
||||
float m_lastVictimTime {}; // time when bot killed an enemy
|
||||
float m_killsInterval {}; // interval between kills
|
||||
|
|
@ -437,7 +437,7 @@ private:
|
|||
bool isEnemyNoTarget (edict_t *enemy);
|
||||
bool isEnemyInDarkArea (edict_t *enemy);
|
||||
bool isFriendInLineOfFire (float distance);
|
||||
bool isGroupOfEnemies (const Vector &location, int numEnemies = 1, float radius = 256.0f);
|
||||
bool isGroupOfEnemies (const Vector &location);
|
||||
bool isPenetrableObstacle (const Vector &dest);
|
||||
bool isPenetrableObstacle1 (const Vector &dest, int penetratePower);
|
||||
bool isPenetrableObstacle2 (const Vector &dest, int penetratePower);
|
||||
|
|
@ -831,11 +831,6 @@ private:
|
|||
return m_weaponType == WeaponType::Sniper;
|
||||
}
|
||||
|
||||
// returns true if bot is using a sniper rifle (awp)
|
||||
bool usesSniperAWP () const {
|
||||
return m_currentWeapon == Weapon::AWP;
|
||||
}
|
||||
|
||||
// returns true if bot is using a rifle
|
||||
bool usesRifle () const {
|
||||
return usesZoomableRifle () || m_weaponType == WeaponType::Rifle;
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ void Bot::avoidGrenades () {
|
|||
const auto &activeGrenades = bots.getActiveGrenades ();
|
||||
|
||||
// find all grenades on the map
|
||||
for (auto pent : activeGrenades) {
|
||||
for (const auto &pent : activeGrenades) {
|
||||
if (pent->v.effects & EF_NODRAW) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -96,10 +96,7 @@ void Bot::avoidGrenades () {
|
|||
}
|
||||
auto model = pent->v.model.str (9);
|
||||
|
||||
if (m_preventFlashing < game.time ()
|
||||
&& cv_whose_your_daddy
|
||||
&& model == kFlashbangModelName) {
|
||||
|
||||
if (m_preventFlashing < game.time () && model == kFlashbangModelName) {
|
||||
// don't look at flash bang
|
||||
if (!(m_states & Sense::SeeingEnemy)) {
|
||||
m_lookAt.y = cr::wrapAngle ((game.getEntityOrigin (pent) - getEyesPos ()).angles ().y + 180.0f);
|
||||
|
|
@ -180,9 +177,7 @@ void Bot::checkBreakablesAround () {
|
|||
|| !game.hasBreakables ()
|
||||
|| m_seeEnemyTime + 4.0f > game.time ()
|
||||
|| !game.isNullEntity (m_enemy)
|
||||
|| !hasPrimaryWeapon ()
|
||||
|| (m_aimFlags & (AimFlags::Danger | AimFlags::PredictPath | AimFlags::Enemy))
|
||||
|| getCurrentTaskId () != Task::Normal) {
|
||||
|| !hasPrimaryWeapon ()) {
|
||||
return;
|
||||
}
|
||||
const auto radius = cv_object_destroy_radius.as <float> ();
|
||||
|
|
@ -349,13 +344,13 @@ void Bot::updatePickups () {
|
|||
bool itemExists = false;
|
||||
auto pickupItem = m_pickupItem;
|
||||
|
||||
for (auto &ent : interesting) {
|
||||
for (const auto &ent : interesting) {
|
||||
|
||||
// in the periods of updating interesting entities we can get fake ones, that already were picked up, so double check if drawn
|
||||
if (ent->v.effects & EF_NODRAW) {
|
||||
continue;
|
||||
}
|
||||
const Vector &origin = game.getEntityOrigin (ent);
|
||||
const auto &origin = game.getEntityOrigin (ent);
|
||||
|
||||
// too far from us ?
|
||||
if (pev->origin.distanceSq (origin) > radiusSq) {
|
||||
|
|
@ -595,7 +590,7 @@ void Bot::updatePickups () {
|
|||
m_defendedBomb = true;
|
||||
|
||||
const int index = findDefendNode (origin);
|
||||
const Path &path = graph[index];
|
||||
const auto &path = graph[index];
|
||||
|
||||
const float bombTimer = mp_c4timer.as <float> ();
|
||||
const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin);
|
||||
|
|
@ -638,7 +633,7 @@ void Bot::updatePickups () {
|
|||
|
||||
// don't steal hostage from human teammate (hack)
|
||||
if (allowPickup) {
|
||||
for (auto &client : util.getClients ()) {
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||
allowPickup = false;
|
||||
|
|
@ -805,9 +800,9 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
|||
float nearestDistance = kInfiniteDistance;
|
||||
|
||||
int lookAtNode = kInvalidNodeIndex;
|
||||
const Path &path = graph[tempIndex];
|
||||
const auto &path = graph[tempIndex];
|
||||
|
||||
for (auto &link : path.links) {
|
||||
for (const auto &link : path.links) {
|
||||
if (link.index == kInvalidNodeIndex) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1658,7 +1653,8 @@ void Bot::overrideConditions () {
|
|||
const auto tid = getCurrentTaskId ();
|
||||
|
||||
// check if we need to escape from bomb
|
||||
if (game.mapIs (MapFlags::Demolition)
|
||||
if ((tid == Task::Normal || tid == Task::MoveToPosition)
|
||||
&& game.mapIs (MapFlags::Demolition)
|
||||
&& bots.isBombPlanted ()
|
||||
&& m_isAlive
|
||||
&& tid != Task::EscapeFromBomb
|
||||
|
|
@ -1717,8 +1713,8 @@ void Bot::overrideConditions () {
|
|||
}
|
||||
|
||||
// special handling for reloading
|
||||
if (!bots.isRoundOver () &&
|
||||
tid == Task::Normal
|
||||
if (!bots.isRoundOver ()
|
||||
&& tid == Task::Normal
|
||||
&& m_reloadState != Reload::None
|
||||
&& m_isReloading
|
||||
&& !isDucking ()
|
||||
|
|
@ -1775,7 +1771,7 @@ void Bot::syncUpdatePredictedIndex () {
|
|||
|
||||
const float distToBotSq = botOrigin.distanceSq (graph[index].origin);
|
||||
|
||||
if (vistab.visible (currentNodeIndex, index) && distToBotSq > 128.0f && distToBotSq < cr::sqrf (768.0f)) {
|
||||
if (vistab.visible (currentNodeIndex, index) && distToBotSq < cr::sqrf (2048.0f)) {
|
||||
bestIndex = index;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1812,7 +1808,8 @@ void Bot::refreshEnemyPredict () {
|
|||
&& distanceToLastEnemySq < cr::sqrf (256.0f)
|
||||
&& m_shootTime + 1.5f > game.time ();
|
||||
|
||||
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
|
||||
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger))
|
||||
&& !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
|
|
@ -1993,8 +1990,8 @@ void Bot::filterTasks () {
|
|||
|
||||
// increase/decrease fear/aggression if bot uses a sniping weapon to be more careful
|
||||
if (usesSniper ()) {
|
||||
tempFear = tempFear * 1.2f;
|
||||
tempAgression = tempAgression * 0.6f;
|
||||
tempFear = tempFear * 1.5f;
|
||||
tempAgression = tempAgression * 0.5f;
|
||||
}
|
||||
auto &filter = bots.getFilters ();
|
||||
|
||||
|
|
@ -2675,7 +2672,7 @@ void Bot::checkRadioQueue () {
|
|||
switch (getCurrentTaskId ()) {
|
||||
case Task::Normal:
|
||||
if (getTask ()->data != kInvalidNodeIndex && rg.chance (70)) {
|
||||
const Path &path = graph[getTask ()->data];
|
||||
const auto &path = graph[getTask ()->data];
|
||||
|
||||
if (path.flags & NodeFlag::Goal) {
|
||||
if (m_hasC4) {
|
||||
|
|
@ -2787,7 +2784,7 @@ void Bot::checkRadioQueue () {
|
|||
int bombPoint = kInvalidNodeIndex;
|
||||
|
||||
// find nearest bomb node to player
|
||||
for (auto &point : graph.m_goalPoints) {
|
||||
for (const auto &point : graph.m_goalPoints) {
|
||||
distanceSq = graph[point].origin.distanceSq (m_radioEntity->v.origin);
|
||||
|
||||
if (distanceSq < nearestDistanceSq) {
|
||||
|
|
@ -2932,7 +2929,7 @@ void Bot::frame () {
|
|||
}
|
||||
|
||||
if (bots.isBombPlanted () && m_team == Team::CT && m_isAlive) {
|
||||
const Vector &bombPosition = graph.getBombOrigin ();
|
||||
const auto &bombPosition = graph.getBombOrigin ();
|
||||
|
||||
if (!m_hasProgressBar
|
||||
&& getCurrentTaskId () != Task::EscapeFromBomb
|
||||
|
|
@ -3057,9 +3054,9 @@ void Bot::logicDuringFreezetime () {
|
|||
return;
|
||||
}
|
||||
|
||||
if (rg.chance (15) && m_jumpTime + rg (1.0f, 2.0f) < game.time ()) {
|
||||
if (rg.chance (15) && m_jumpTime < game.time ()) {
|
||||
pev->button |= IN_JUMP;
|
||||
m_jumpTime = game.time ();
|
||||
m_jumpTime = game.time () + rg (1.0f, 2.0f);
|
||||
}
|
||||
static Array <edict_t *> players {};
|
||||
players.clear ();
|
||||
|
|
@ -3189,7 +3186,7 @@ void Bot::checkSpawnConditions () {
|
|||
void Bot::logic () {
|
||||
// this function gets called each frame and is the core of all bot ai. from here all other subroutines are called
|
||||
|
||||
float movedDistance = 2.0f; // length of different vector (distance bot moved)
|
||||
float movedDistance = 4.0f; // length of different vector (distance bot moved)
|
||||
|
||||
resetMovement ();
|
||||
|
||||
|
|
@ -3251,7 +3248,7 @@ void Bot::logic () {
|
|||
else if (!hasFriendNearby
|
||||
&& rg.chance (50)
|
||||
&& game.getTeam (m_enemy) != m_team
|
||||
&& isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) {
|
||||
&& isGroupOfEnemies (m_enemy->v.origin)) {
|
||||
|
||||
pushChatterMessage (Chatter::ScaredEmotion);
|
||||
}
|
||||
|
|
@ -3303,8 +3300,8 @@ void Bot::logic () {
|
|||
setIdealReactionTimers ();
|
||||
|
||||
// calculate 2 direction vectors, 1 without the up/down component
|
||||
const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval);
|
||||
const Vector &dirNormal = dirOld.normalize2d_apx ();
|
||||
const auto &dirOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval);
|
||||
const auto &dirNormal = dirOld.normalize2d_apx ();
|
||||
|
||||
m_moveAngles = dirOld.angles ();
|
||||
m_moveAngles.clampAngles ();
|
||||
|
|
@ -3340,8 +3337,17 @@ void Bot::logic () {
|
|||
// don't duck to get away faster
|
||||
pev->button &= ~IN_DUCK;
|
||||
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
|
||||
Vector right {}, forward {};
|
||||
pev->v_angle.angleVectors (&forward, &right, nullptr);
|
||||
|
||||
const auto &front = forward * -pev->maxspeed * 0.2f;
|
||||
const auto &side = right * pev->maxspeed * static_cast <float> (m_needAvoidGrenade) * 0.2f;
|
||||
const auto &spot = pev->origin + front + side + pev->velocity * m_frameInterval;
|
||||
|
||||
if (!isDeadlyMove (spot)) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we're not stuck picking something
|
||||
|
|
@ -3836,7 +3842,7 @@ Vector Bot::isBombAudible () {
|
|||
if (m_difficulty > Difficulty::Hard) {
|
||||
return graph.getBombOrigin ();
|
||||
}
|
||||
const Vector &bombOrigin = graph.getBombOrigin ();
|
||||
const auto &bombOrigin = graph.getBombOrigin ();
|
||||
|
||||
const float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.as <float> ()) * 100.0f;
|
||||
float desiredRadius = 768.0f;
|
||||
|
|
@ -3865,8 +3871,8 @@ Vector Bot::isBombAudible () {
|
|||
bool Bot::canRunHeavyWeight () {
|
||||
constexpr auto kInterval = 1.0f / 10.0f;
|
||||
|
||||
if (m_heavyTimestamp + kInterval < game.time ()) {
|
||||
m_heavyTimestamp = game.time ();
|
||||
if (m_heavyTimestamp < game.time ()) {
|
||||
m_heavyTimestamp = game.time () + kInterval;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -3981,7 +3987,7 @@ void Bot::updateHearing () {
|
|||
float nearestDistanceSq = kInfiniteDistance;
|
||||
|
||||
// do not hear to other enemies if just tracked old one
|
||||
if (m_timeNextTracking < game.time () && util.isAlive (m_lastEnemy) && m_lastEnemy == m_trackingEdict) {
|
||||
if (m_timeNextTracking < game.time () && m_lastEnemy == m_trackingEdict && util.isAlive (m_lastEnemy)) {
|
||||
m_hearedEnemy = m_lastEnemy;
|
||||
m_lastEnemyOrigin = m_lastEnemy->v.origin;
|
||||
|
||||
|
|
|
|||
|
|
@ -133,10 +133,11 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) {
|
|||
if (!cv_check_darkness && game.isNullEntity (enemy)) {
|
||||
return false;
|
||||
}
|
||||
const auto &v = enemy->v;
|
||||
const auto scolor = illum.getSkyColor ();
|
||||
|
||||
// check if node near the enemy have a degraded light level
|
||||
const auto enemyNodeIndex = graph.getNearest (enemy->v.origin);
|
||||
const auto enemyNodeIndex = graph.getNearest (v.origin);
|
||||
|
||||
if (!graph.exists (enemyNodeIndex)) {
|
||||
return false;
|
||||
|
|
@ -149,9 +150,9 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) {
|
|||
}
|
||||
bool enemySemiTransparent = false;
|
||||
|
||||
const bool enemyHasGun = (enemy->v.weapons & kPrimaryWeaponMask) || (enemy->v.weapons & kSecondaryWeaponMask);
|
||||
const bool enemyHasFlashlightEnabled = !!(enemy->v.effects & EF_DIMLIGHT);
|
||||
const bool enemyIsAttacking = !!(enemy->v.oldbuttons & IN_ATTACK);
|
||||
const bool enemyHasGun = (v.weapons & kPrimaryWeaponMask) || (v.weapons & kSecondaryWeaponMask);
|
||||
const bool enemyIsAttacking = (v.button & IN_ATTACK) || (v.oldbuttons & IN_ATTACK);
|
||||
const bool enemyHasFlashlightEnabled = !!(v.effects & EF_DIMLIGHT);
|
||||
|
||||
if (!m_usesNVG && ((llevel < 3.0f && scolor > 50.0f) || (llevel < 25.0f && scolor <= 50.0f))
|
||||
&& !enemyHasFlashlightEnabled && (!enemyIsAttacking || !enemyHasGun)) {
|
||||
|
|
@ -163,7 +164,7 @@ bool Bot::isEnemyInDarkArea (edict_t *enemy) {
|
|||
enemySemiTransparent = true;
|
||||
}
|
||||
TraceResult result {};
|
||||
game.testLine (getEyesPos (), enemy->v.origin, m_isCreature ? TraceIgnore::None : TraceIgnore::Everything, ent (), &result);
|
||||
game.testLine (getEyesPos (), v.origin, m_isCreature ? TraceIgnore::None : TraceIgnore::Everything, ent (), &result);
|
||||
|
||||
return (result.flFraction <= 1.0f && result.pHit == enemy && (m_usesNVG || !enemySemiTransparent));
|
||||
}
|
||||
|
|
@ -193,7 +194,7 @@ bool Bot::checkBodyPartsWithOffsets (edict_t *target) {
|
|||
auto self = pev->pContainingEntity;
|
||||
|
||||
// creatures can't hurt behind anything
|
||||
const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ?TraceIgnore::Monsters : TraceIgnore::Everything);
|
||||
const auto ignoreFlags = m_isCreature ? TraceIgnore::None : (cv_aim_trace_consider_glass ? TraceIgnore::Monsters : TraceIgnore::Everything);
|
||||
|
||||
const auto hitsTarget = [&] () -> bool {
|
||||
return result.flFraction >= 1.0f || result.pHit == target;
|
||||
|
|
@ -390,7 +391,7 @@ bool Bot::lookupEnemies () {
|
|||
m_states |= Sense::SuspectEnemy;
|
||||
|
||||
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f
|
||||
&& pev->origin.distanceSq2d (m_lastEnemyOrigin) < cr::sqrf (256.0f)
|
||||
&& m_lastEnemyOrigin.distanceSq (pev->origin) < cr::sqrf (256.0f)
|
||||
&& m_shootTime + 1.5f > game.time ();
|
||||
|
||||
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath | AimFlags::Danger))
|
||||
|
|
@ -1244,10 +1245,9 @@ void Bot::fireWeapons () {
|
|||
if (cv_stab_close_enemies && m_difficulty >= Difficulty::Normal
|
||||
&& m_healthValue > 80.0f
|
||||
&& !game.isNullEntity (m_enemy)
|
||||
&& m_healthValue >= m_enemy->v.health
|
||||
&& distance < 100.0f
|
||||
&& !isOnLadder ()
|
||||
&& !isGroupOfEnemies (pev->origin)) {
|
||||
&& !isGroupOfEnemies (pev->origin)
|
||||
&& getCurrentTaskId () != Task::Camp) {
|
||||
|
||||
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
|
||||
return;
|
||||
|
|
@ -1428,13 +1428,14 @@ void Bot::attackMovement () {
|
|||
approach = 49;
|
||||
}
|
||||
}
|
||||
const bool isEnemyCone = isInViewCone (m_enemy->v.origin);
|
||||
|
||||
// only take cover when bomb is not planted and enemy can see the bot or the bot is VIP
|
||||
if (!game.is (GameFlags::CSDM) && !isKnifeMode ()) {
|
||||
if ((m_states & Sense::SeeingEnemy)
|
||||
&& approach < 30
|
||||
&& !bots.isBombPlanted ()
|
||||
&& (isInViewCone (m_enemy->v.origin) || m_isVIP || m_isReloading)) {
|
||||
&& (isEnemyCone || m_isVIP || m_isReloading)) {
|
||||
|
||||
if (m_retreatTime < game.time ()) {
|
||||
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
|
||||
|
|
@ -1467,10 +1468,7 @@ void Bot::attackMovement () {
|
|||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
else if (distanceSq < cr::sqrf (1024.0f)) {
|
||||
if (isGroupOfEnemies (m_enemy->v.origin)) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
else if (rand < (usesSubmachine () ? 50 : 30)) {
|
||||
if (rand < (usesSubmachine () ? 50 : 30)) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
else {
|
||||
|
|
@ -1478,10 +1476,7 @@ void Bot::attackMovement () {
|
|||
}
|
||||
}
|
||||
else {
|
||||
if (isGroupOfEnemies (m_enemy->v.origin)) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
else if (rand < (usesSubmachine () ? 80 : 90)) {
|
||||
if (rand < (usesSubmachine () ? 80 : 90)) {
|
||||
m_fightStyle = Fight::Stay;
|
||||
}
|
||||
else {
|
||||
|
|
@ -1505,7 +1500,12 @@ void Bot::attackMovement () {
|
|||
// fire hurts friend value here is from previous frame, but acceptable, and saves us alot of cpu cycles
|
||||
if (approach < 30 || m_fireHurtsFriend || ((usesPistol () || usesShotgun ())
|
||||
&& distanceSq < cr::sqrf (pistolStrafeDistance)
|
||||
&& isInViewCone (m_enemyOrigin))) {
|
||||
&& isEnemyCone)) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
const auto enemyWeaponIsSniper = (m_enemy->v.weapons & kSniperWeaponMask);
|
||||
|
||||
if (enemyWeaponIsSniper && isEnemyCone) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
m_lastFightStyleCheck = game.time () + 3.0f;
|
||||
|
|
@ -1515,7 +1515,7 @@ void Bot::attackMovement () {
|
|||
m_moveSpeed = -pev->maxspeed;
|
||||
}
|
||||
|
||||
if (usesKnife () && isInViewCone (m_enemy->v.origin)) {
|
||||
if (usesKnife () && isEnemyCone) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
|
||||
|
|
@ -1624,7 +1624,7 @@ void Bot::attackMovement () {
|
|||
|
||||
if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) {
|
||||
if (distanceSq < cr::sqrf (kSprayDistanceX2)) {
|
||||
if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
|
||||
if (rg (0, 1000) < rg (5, 10) && pev->velocity.length2d () > 150.0f && isEnemyCone) {
|
||||
pev->button |= IN_JUMP;
|
||||
}
|
||||
}
|
||||
|
|
@ -2003,28 +2003,29 @@ void Bot::updateTeamCommands () {
|
|||
m_timeTeamOrder = game.time () + rg (15.0f, 30.0f);
|
||||
}
|
||||
|
||||
bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) {
|
||||
bool Bot::isGroupOfEnemies (const Vector &location) {
|
||||
int numPlayers = 0;
|
||||
|
||||
// needs a square radius
|
||||
const float radiusSq = cr::sqrf (radius);
|
||||
const float radiusSq = cr::sqrf (768.0f);
|
||||
|
||||
// search the world for enemy players...
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent ()) {
|
||||
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team || client.ent == ent ()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (client.ent->v.origin.distanceSq (location) < radiusSq) {
|
||||
if (client.team == m_team) {
|
||||
return false; // don't target our teammates...
|
||||
}
|
||||
|
||||
if (numPlayers++ > numEnemies) {
|
||||
return true;
|
||||
if (!seesEntity (client.origin)) {
|
||||
continue;
|
||||
}
|
||||
++numPlayers;
|
||||
}
|
||||
}
|
||||
|
||||
if (numPlayers < 2) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -2541,7 +2542,7 @@ bool Bot::isEnemyNoticeable (float range) {
|
|||
if (isCrouching) {
|
||||
// crouching and motionless - very tough to notice
|
||||
closeChance = 80.0f;
|
||||
farChance = 5.0f; // takes about three seconds to notice (50% chance)
|
||||
farChance = 5.0f; // takes about three seconds to notice (50% chance)
|
||||
}
|
||||
// standing
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -1509,6 +1509,7 @@ void Bot::newRound () {
|
|||
m_lastBreakable = nullptr;
|
||||
|
||||
m_timeDoorOpen = 0.0f;
|
||||
m_timeHitDoor = 0.0f;
|
||||
|
||||
for (auto &fall : m_checkFallPoint) {
|
||||
fall.clear ();
|
||||
|
|
@ -1624,7 +1625,6 @@ void Bot::newRound () {
|
|||
m_dodgeStrafeDir = Dodge::None;
|
||||
m_fightStyle = Fight::None;
|
||||
m_lastFightStyleCheck = 0.0f;
|
||||
m_stuckTimestamp = 0.0f;
|
||||
|
||||
m_checkWeaponSwitch = true;
|
||||
m_checkKnifeSwitch = true;
|
||||
|
|
@ -2209,11 +2209,11 @@ void BotThreadWorker::startup (int workers) {
|
|||
m_botWorker.startup (static_cast <size_t> (requestedThreads));
|
||||
}
|
||||
|
||||
bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, float grenadeBloat) {
|
||||
bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
|
||||
if (m_activeGrenades.empty ()) {
|
||||
return false;
|
||||
}
|
||||
constexpr auto kSmokeGrenadeRadius = 115;
|
||||
constexpr auto kSmokeGrenadeRadius = 115.0f;
|
||||
|
||||
// distance along line of sight covered by smoke
|
||||
float totalSmokedLength = 0.0f;
|
||||
|
|
@ -2241,7 +2241,7 @@ bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to, flo
|
|||
continue;
|
||||
}
|
||||
|
||||
const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius) * cr::sqrf (grenadeBloat);
|
||||
const float smokeRadiusSq = cr::sqrf (kSmokeGrenadeRadius);
|
||||
const Vector &smokeOrigin = game.getEntityOrigin (pent);
|
||||
|
||||
Vector toGrenade = smokeOrigin - from;
|
||||
|
|
|
|||
206
src/navigate.cpp
206
src/navigate.cpp
|
|
@ -99,6 +99,7 @@ int Bot::findBestGoal () {
|
|||
}
|
||||
return findGoalPost (hasMoreHostagesAround ? GoalTactic::Goal : GoalTactic::RescueHostage, defensiveNodes, offensiveNodes);
|
||||
}
|
||||
constexpr float kBehaviorBase = 30.0f;
|
||||
const auto difficulty = static_cast <float> (m_difficulty);
|
||||
|
||||
offensive = m_agressionLevel * 100.0f;
|
||||
|
|
@ -107,27 +108,27 @@ int Bot::findBestGoal () {
|
|||
if (game.mapIs (MapFlags::Assassination | MapFlags::HostageRescue)) {
|
||||
if (m_team == Team::Terrorist) {
|
||||
if (m_personality == Personality::Rusher) {
|
||||
defensive -= 25.0f - difficulty * 0.5f;
|
||||
offensive += 25.0f + difficulty * 5.0f;
|
||||
defensive -= kBehaviorBase - difficulty * 0.5f;
|
||||
offensive += kBehaviorBase + difficulty * 5.0f;
|
||||
}
|
||||
else if (m_personality == Personality::Normal && rg.chance (40)) {
|
||||
defensive -= 25.0f;
|
||||
offensive += 25.0f;
|
||||
defensive -= kBehaviorBase;
|
||||
offensive += kBehaviorBase;
|
||||
}
|
||||
else {
|
||||
defensive += 25.0f;
|
||||
offensive -= 25.0f;
|
||||
defensive += kBehaviorBase;
|
||||
offensive -= kBehaviorBase;
|
||||
}
|
||||
}
|
||||
else if (m_team == Team::CT) {
|
||||
// on hostage maps force more bots to save hostages
|
||||
if (game.mapIs (MapFlags::HostageRescue)) {
|
||||
defensive -= 25.0f - difficulty * 0.5f;
|
||||
offensive += 25.0f + difficulty * 5.0f;
|
||||
defensive -= kBehaviorBase - difficulty * 0.5f;
|
||||
offensive += kBehaviorBase + difficulty * 5.0f;
|
||||
}
|
||||
else {
|
||||
defensive -= 25.0f;
|
||||
offensive += 25.0f;
|
||||
defensive -= kBehaviorBase;
|
||||
offensive += kBehaviorBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -140,8 +141,8 @@ int Bot::findBestGoal () {
|
|||
}
|
||||
return m_chosenGoalIndex = findBombNode ();
|
||||
}
|
||||
defensive += 25.0f + difficulty * 4.0f;
|
||||
offensive -= 25.0f - difficulty * 0.5f;
|
||||
defensive += kBehaviorBase + difficulty * 5.0f;
|
||||
offensive -= kBehaviorBase - difficulty * 0.5f;
|
||||
|
||||
if (m_personality != Personality::Rusher) {
|
||||
defensive += 10.0f;
|
||||
|
|
@ -155,12 +156,12 @@ int Bot::findBestGoal () {
|
|||
}
|
||||
else if (game.mapIs (MapFlags::Escape)) {
|
||||
if (m_team == Team::Terrorist) {
|
||||
offensive += 25.0f + difficulty * 4.0f;
|
||||
defensive -= 25.0f - difficulty * 0.5f;
|
||||
offensive += kBehaviorBase + difficulty * 5.0f;
|
||||
defensive -= kBehaviorBase - difficulty * 0.5f;
|
||||
}
|
||||
else if (m_team == Team::CT) {
|
||||
offensive -= 25.0f - difficulty * 4.5f;
|
||||
defensive += 25.0f + difficulty * 0.5f;
|
||||
offensive -= kBehaviorBase - difficulty * 5.0f;
|
||||
defensive += kBehaviorBase + difficulty * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +189,7 @@ int Bot::findBestGoal () {
|
|||
}
|
||||
|
||||
if (goalDesire > tacticChoice) {
|
||||
tacticChoice = goalDesire;
|
||||
tactic = GoalTactic::Goal;
|
||||
}
|
||||
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
|
||||
|
|
@ -230,7 +232,7 @@ int Bot::findBestGoalWhenBombAction () {
|
|||
m_defendedBomb = true;
|
||||
|
||||
result = findDefendNode (bombOrigin);
|
||||
const Path &path = graph[result];
|
||||
const auto &path = graph[result];
|
||||
|
||||
const float bombTimer = mp_c4timer.as <float> ();
|
||||
const float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin);
|
||||
|
|
@ -258,7 +260,11 @@ int Bot::findBestGoalWhenBombAction () {
|
|||
}
|
||||
|
||||
int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
|
||||
int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
|
||||
int goalChoices[4] {};
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
goalChoices[i] = kInvalidNodeIndex;
|
||||
}
|
||||
|
||||
if (tactic == GoalTactic::Defensive && !(*defensive).empty ()) { // careful goal
|
||||
postProcessGoals (*defensive, goalChoices);
|
||||
|
|
@ -282,7 +288,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
|
|||
float nearestDistanceSq = kInfiniteDistance;
|
||||
int count = 0;
|
||||
|
||||
for (auto &point : graph.m_goalPoints) {
|
||||
for (const auto &point : graph.m_goalPoints) {
|
||||
const float distanceSq = graph[point].origin.distanceSq (pev->origin);
|
||||
|
||||
if (distanceSq > cr::sqrf (1024.0f)) {
|
||||
|
|
@ -313,7 +319,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
|
|||
float nearestDistanceSq = kInfiniteDistance;
|
||||
int count = 0;
|
||||
|
||||
for (auto &point : graph.m_rescuePoints) {
|
||||
for (const auto &point : graph.m_rescuePoints) {
|
||||
const float distanceSq = graph[point].origin.distanceSq (pev->origin);
|
||||
|
||||
if (distanceSq < nearestDistanceSq) {
|
||||
|
|
@ -334,10 +340,6 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offensive) {
|
|||
}
|
||||
ensureCurrentNodeIndex ();
|
||||
|
||||
if (goalChoices[0] == kInvalidNodeIndex) {
|
||||
return m_chosenGoalIndex = graph.random ();
|
||||
}
|
||||
|
||||
// rusher bots does not care any danger (idea from pbmm)
|
||||
if (m_personality == Personality::Rusher) {
|
||||
const auto randomGoal = goalChoices[rg (0, 3)];
|
||||
|
|
@ -468,39 +470,23 @@ void Bot::ignoreCollision () {
|
|||
}
|
||||
|
||||
void Bot::doPlayerAvoidance (const Vector &normal) {
|
||||
if (cv_has_team_semiclip || game.is (GameFlags::FreeForAll)) {
|
||||
if (isOnLadder () || cv_has_team_semiclip || game.is (GameFlags::FreeForAll)) {
|
||||
return; // no player avoiding when with semiclip plugin
|
||||
}
|
||||
|
||||
m_hindrance = nullptr;
|
||||
float distanceSq = cr::sqrf (512.0f);
|
||||
|
||||
if (isOnLadder ()) {
|
||||
return;
|
||||
}
|
||||
const auto ownPrio = bots.getPlayerPriority (ent ());
|
||||
|
||||
// find nearest player to bot
|
||||
for (const auto &client : util.getClients ()) {
|
||||
|
||||
// need only good meat
|
||||
if (!(client.flags & ClientFlags::Used)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// and still alive meet
|
||||
if (!(client.flags & ClientFlags::Alive)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// our team, alive and not myself?
|
||||
if (client.team != m_team || client.ent == ent ()) {
|
||||
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) {
|
||||
continue;
|
||||
}
|
||||
const auto otherPrio = bots.getPlayerPriority (client.ent);
|
||||
|
||||
// give some priorities to bot avoidance
|
||||
if (ownPrio > otherPrio) {
|
||||
if (ownPrio < otherPrio) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +495,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
|||
const auto avoidPrio = bots.getPlayerPriority (m_hindrance);
|
||||
|
||||
// ignore because we're already avoiding someone better
|
||||
if (avoidPrio > otherPrio) {
|
||||
if (avoidPrio < otherPrio) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -525,7 +511,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
|||
if (game.isNullEntity (m_hindrance)) {
|
||||
return;
|
||||
}
|
||||
const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f);
|
||||
const float interval = m_frameInterval * (!isDucking () && pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f);
|
||||
|
||||
// use our movement angles, try to predict where we should be next frame
|
||||
Vector right {}, forward {};
|
||||
|
|
@ -551,10 +537,13 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
|
|||
setStrafeSpeed (normal, -pev->maxspeed);
|
||||
}
|
||||
|
||||
if (distanceSq < cr::sqrf (80.0f)) {
|
||||
if (distanceSq < cr::sqrf (96.0f)) {
|
||||
if ((dir | forward.normalize2d_apx ()) < 0.0f) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
}
|
||||
else {
|
||||
m_moveSpeed = pev->maxspeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -568,7 +557,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
|
|||
// standing still, no need to check?
|
||||
if (m_lastCollTime < game.time () && getCurrentTaskId () != Task::Attack) {
|
||||
// didn't we move enough previously?
|
||||
if (movedDistance < 2.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) {
|
||||
if (movedDistance < 4.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) {
|
||||
m_prevTime = game.time (); // then consider being stuck
|
||||
m_isStuck = true;
|
||||
|
||||
|
|
@ -862,7 +851,7 @@ void Bot::checkFall () {
|
|||
}
|
||||
}
|
||||
else if (!isOnLadder () && !isInWater ()) {
|
||||
if (!m_checkFallPoint[0].empty () && !m_checkFallPoint[1].empty ()) {
|
||||
if (!m_checkFallPoint[0].empty () || !m_checkFallPoint[1].empty ()) {
|
||||
m_checkFall = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -882,12 +871,12 @@ void Bot::checkFall () {
|
|||
&& baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
|
||||
fixFall = true;
|
||||
}
|
||||
else if (pev->origin.z + 128.0f < m_checkFallPoint[1].z && pev->origin.z + 128.0f < m_checkFallPoint[0].z) {
|
||||
else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f || m_checkFallPoint[0].z > pev->origin.z + 128.0f) {
|
||||
fixFall = true;
|
||||
}
|
||||
else if (m_currentNodeIndex != kInvalidNodeIndex
|
||||
&& nowDistanceSq <= cr::sqrf (32.0f)
|
||||
&& pev->origin.z + 64.0f < m_checkFallPoint[1].z) {
|
||||
&& nowDistanceSq > cr::sqrf (16.0f)
|
||||
&& m_checkFallPoint[1].z > pev->origin.z + 62.0f) {
|
||||
fixFall = true;
|
||||
}
|
||||
|
||||
|
|
@ -925,6 +914,26 @@ void Bot::moveToGoal () {
|
|||
pev->button |= IN_DUCK;
|
||||
}
|
||||
}
|
||||
|
||||
// press jump button if we need to leave the ladder
|
||||
if (!(m_pathFlags & NodeFlag::Ladder)
|
||||
&& isPreviousLadder ()
|
||||
&& isOnFloor ()
|
||||
&& isOnLadder ()
|
||||
&& m_moveSpeed > 50.0f
|
||||
&& pev->velocity.lengthSq () < 50.0f) {
|
||||
|
||||
pev->button |= IN_JUMP;
|
||||
m_jumpTime = game.time () + 1.0f;
|
||||
}
|
||||
const auto distanceSq2d = m_destOrigin.distanceSq2d (pev->origin + pev->velocity * m_frameInterval);
|
||||
|
||||
if (distanceSq2d < cr::sqrf (m_moveSpeed) * m_frameInterval && getTask ()->data != kInvalidNodeIndex) {
|
||||
m_moveSpeed = distanceSq2d;
|
||||
}
|
||||
if (m_moveSpeed > pev->maxspeed) {
|
||||
m_moveSpeed = pev->maxspeed;
|
||||
}
|
||||
m_lastUsedNodesTime = game.time ();
|
||||
|
||||
// special movement for swimming here
|
||||
|
|
@ -1083,8 +1092,10 @@ bool Bot::updateNavigation () {
|
|||
m_approachingLadderTimer.start (m_frameInterval * 4.0f);
|
||||
}
|
||||
|
||||
if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !isDucking ()) {
|
||||
m_moveSpeed = ladderDistance;
|
||||
if (!isOnLadder () && isOnFloor () && !isDucking ()) {
|
||||
if (!isPreviousLadder ()) {
|
||||
m_moveSpeed = ladderDistance;
|
||||
}
|
||||
|
||||
if (m_moveSpeed < 150.0f) {
|
||||
m_moveSpeed = 150.0f;
|
||||
|
|
@ -1107,17 +1118,14 @@ bool Bot::updateNavigation () {
|
|||
|
||||
bool foundGround = false;
|
||||
|
||||
if (cr::abs (pev->origin.z - client.ent->v.origin.z) > 15.0f) {
|
||||
if (isPreviousLadder ()) {
|
||||
if (client.ent->v.origin.distanceSq (pev->origin) < cr::sqrf (64.0f)) {
|
||||
foundGround = true;
|
||||
}
|
||||
}
|
||||
if (!isPreviousLadder ()
|
||||
&& client.ent->v.origin.distanceSq (pev->origin) < cr::sqrf (128.0f)
|
||||
&& cr::abs (client.ent->v.origin.z - pev->origin.z) > 17.0f) {
|
||||
foundGround = true;
|
||||
}
|
||||
|
||||
if (foundGround) {
|
||||
clearSearchNodes ();
|
||||
findNextBestNode ();
|
||||
changeNodeIndex (prevNodeIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1140,8 +1148,11 @@ bool Bot::updateNavigation () {
|
|||
game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr);
|
||||
|
||||
if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && util.isDoorEntity (tr.pHit)) {
|
||||
const auto &origin = game.getEntityOrigin (tr.pHit);
|
||||
const float distanceSq = pev->origin.distanceSq (origin);
|
||||
|
||||
// if the door is near enough...
|
||||
if (pev->origin.distanceSq (game.getEntityOrigin (tr.pHit)) < cr::sqrf (50.0f)) {
|
||||
if (distanceSq < cr::sqrf (56.0f)) {
|
||||
ignoreCollision (); // don't consider being stuck
|
||||
|
||||
// also 'use' the door randomly
|
||||
|
|
@ -1175,8 +1186,8 @@ bool Bot::updateNavigation () {
|
|||
}
|
||||
|
||||
// if bot hits the door, then it opens, so wait a bit to let it open safely
|
||||
if (pev->velocity.lengthSq2d () < cr::sqrf (4.0f) && m_timeDoorOpen < game.time ()) {
|
||||
if (getCurrentTaskId () != Task::MoveToPosition) {
|
||||
if (pev->velocity.lengthSq2d () < cr::sqrf (10.0f) && m_timeDoorOpen < game.time ()) {
|
||||
if (m_timeHitDoor >= game.time ()) {
|
||||
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.5f, false);
|
||||
}
|
||||
m_timeDoorOpen = game.time () + 1.0f; // retry in 1 sec until door is open
|
||||
|
|
@ -1205,10 +1216,25 @@ bool Bot::updateNavigation () {
|
|||
else if (m_tryOpenDoor > 4) {
|
||||
m_tryOpenDoor = 0;
|
||||
|
||||
// go back to prev node, if blocked for long time
|
||||
if (graph.exists (m_previousNodes[0])) {
|
||||
startTask (Task::MoveToPosition, TaskPri::MoveToPosition, m_previousNodes[0], game.time () + 3.0f, true);
|
||||
if (rg.chance (50)) {
|
||||
const auto prevNode = m_previousNodes[0];
|
||||
|
||||
// go back to prev node, if blocked for long time
|
||||
if (graph.exists (prevNode)) {
|
||||
changeNodeIndex (prevNode);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const auto &dirToPoint = (pev->origin - origin).normalize2d_apx ();
|
||||
const auto &forwardMove = m_moveAngles.forward ().normalize2d_apx ();
|
||||
|
||||
if (distanceSq < cr::sqrf (80.0f)) {
|
||||
if ((dirToPoint | forwardMove) < 0.0f && !checkWallOnBehind ()) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_timeHitDoor = game.time () + 3.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1307,7 +1333,7 @@ bool Bot::updateNavigation () {
|
|||
&& getCurrentTaskId () != Task::EscapeFromBomb
|
||||
&& taskTarget != kInvalidNodeIndex) {
|
||||
|
||||
const Vector &bombOrigin = isBombAudible ();
|
||||
const auto &bombOrigin = isBombAudible ();
|
||||
|
||||
// bot within 'hearable' bomb tick noises?
|
||||
if (!bombOrigin.empty ()) {
|
||||
|
|
@ -1404,7 +1430,7 @@ bool Bot::updateLiftHandling () {
|
|||
}
|
||||
else if (!m_pathWalk.empty ()) { // no lift found at node
|
||||
if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor) && m_pathWalk.hasNext ()) {
|
||||
int nextNode = m_pathWalk.next ();
|
||||
const auto nextNode = m_pathWalk.next ();
|
||||
|
||||
if (graph.exists (nextNode) && (graph[nextNode].flags & NodeFlag::Lift)) {
|
||||
game.testLine (m_path->origin, graph[nextNode].origin, TraceIgnore::Everything, ent (), &tr);
|
||||
|
|
@ -1727,8 +1753,13 @@ bool Bot::findNextBestNode () {
|
|||
|
||||
int busyIndex = kInvalidNodeIndex;
|
||||
|
||||
float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance };
|
||||
int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
|
||||
float lessDist[3] {};
|
||||
int lessIndex[3] {};
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
lessDist[i] = kInfiniteDistance;
|
||||
lessIndex[i] = kInvalidNodeIndex;
|
||||
}
|
||||
|
||||
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
|
||||
const auto &bucket = graph.getNodesInBucket (origin);
|
||||
|
|
@ -1785,19 +1816,10 @@ bool Bot::findNextBestNode () {
|
|||
const float distanceSq = pev->origin.distanceSq (path.origin);
|
||||
|
||||
if (distanceSq < lessDist[0]) {
|
||||
lessDist[2] = lessDist[1];
|
||||
lessIndex[2] = lessIndex[1];
|
||||
|
||||
lessDist[1] = lessDist[0];
|
||||
lessIndex[1] = lessIndex[0];
|
||||
|
||||
lessDist[0] = distanceSq;
|
||||
lessIndex[0] = path.number;
|
||||
}
|
||||
else if (distanceSq < lessDist[1]) {
|
||||
lessDist[2] = lessDist[1];
|
||||
lessIndex[2] = lessIndex[1];
|
||||
|
||||
lessDist[1] = distanceSq;
|
||||
lessIndex[1] = path.number;
|
||||
}
|
||||
|
|
@ -1888,7 +1910,7 @@ void Bot::findValidNode () {
|
|||
damageValue = cr::clamp (damageValue + 100, 0, kMaxDamageValue);
|
||||
|
||||
// affect nearby connected with victim nodes
|
||||
for (auto &neighbour : m_path->links) {
|
||||
for (const auto &neighbour : m_path->links) {
|
||||
if (graph.exists (neighbour.index)) {
|
||||
int neighbourValue = practice.getDamage (team, neighbour.index, neighbour.index);
|
||||
neighbourValue = cr::clamp (neighbourValue + 100, 0, kMaxDamageValue);
|
||||
|
|
@ -2019,7 +2041,7 @@ int Bot::findBombNode () {
|
|||
float lastDistanceSq = kInfiniteDistance;
|
||||
|
||||
// find nearest goal node either to bomb (if "heard" or player)
|
||||
for (auto &point : goals) {
|
||||
for (const auto &point : goals) {
|
||||
const float distanceSq = bomb.distanceSq (graph[point].origin);
|
||||
|
||||
// check if we got more close distance
|
||||
|
|
@ -2211,7 +2233,7 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
}
|
||||
|
||||
// now get enemies neigbouring points
|
||||
for (auto &link : graph[enemyIndex].links) {
|
||||
for (const auto &link : graph[enemyIndex].links) {
|
||||
if (link.index != kInvalidNodeIndex) {
|
||||
enemies.push (link.index);
|
||||
}
|
||||
|
|
@ -2228,7 +2250,7 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
}
|
||||
bool neighbourVisible = false; // now check neighbour nodes for visibility
|
||||
|
||||
for (auto &enemy : enemies) {
|
||||
for (const auto &enemy : enemies) {
|
||||
if (vistab.visible (enemy, path.number)) {
|
||||
neighbourVisible = true;
|
||||
break;
|
||||
|
|
@ -2422,7 +2444,7 @@ bool Bot::advanceMovement () {
|
|||
|
||||
m_campButtons = 0;
|
||||
|
||||
const int nextIndex = m_pathWalk.next ();
|
||||
const auto nextIndex = m_pathWalk.next ();
|
||||
auto kills = static_cast <float> (practice.getDamage (m_team, nextIndex, nextIndex));
|
||||
|
||||
// if damage done higher than one
|
||||
|
|
@ -2452,7 +2474,7 @@ bool Bot::advanceMovement () {
|
|||
}
|
||||
|
||||
// force terrorist bot to plant bomb
|
||||
if (m_inBombZone && !m_hasProgressBar && m_hasC4 && getCurrentTaskId () != Task::PlantBomb) {
|
||||
if (m_inBombZone && !m_hasProgressBar && m_hasC4 && tid != Task::PlantBomb) {
|
||||
changeNextGoal ();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2462,7 +2484,7 @@ bool Bot::advanceMovement () {
|
|||
if (!m_pathWalk.empty ()) {
|
||||
m_jumpSequence = false;
|
||||
|
||||
const int destIndex = m_pathWalk.first ();
|
||||
const auto destIndex = m_pathWalk.first ();
|
||||
bool isCurrentJump = false;
|
||||
|
||||
// find out about connection flags
|
||||
|
|
@ -3158,13 +3180,13 @@ int Bot::getRandomCampDir () {
|
|||
}
|
||||
|
||||
void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) {
|
||||
const Vector &los = (moveDir - pev->origin).normalize2d_apx ();
|
||||
const auto &los = (moveDir - pev->origin).normalize2d_apx ();
|
||||
const float dot = los | pev->angles.forward ().get2d ();
|
||||
|
||||
if (dot > 0.0f && !checkWallOnRight ()) {
|
||||
m_strafeSpeed = strafeSpeed;
|
||||
}
|
||||
else if (!checkWallOnLeft ()) {
|
||||
else if (dot < 0.0f && !checkWallOnLeft ()) {
|
||||
m_strafeSpeed = -strafeSpeed;
|
||||
}
|
||||
}
|
||||
|
|
@ -3371,15 +3393,10 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
|
|||
fprintf (stderr, "%s source path is same as dest (%d).\n", __func__, destIndex);
|
||||
return;
|
||||
}
|
||||
clearSearchNodes ();
|
||||
|
||||
m_chosenGoalIndex = srcIndex;
|
||||
m_goalValue = 0.0f;
|
||||
|
||||
// always use shortest-path algorithm when failed sanity checks within load
|
||||
if (planner.isPathsCheckFailed ()) {
|
||||
findShortestPath (srcIndex, destIndex);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3414,6 +3431,7 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
|
|||
m_planner->setG (PlannerHeuristic::gfunctionPathDist);
|
||||
}
|
||||
}
|
||||
clearSearchNodes ();
|
||||
|
||||
m_chosenGoalIndex = srcIndex;
|
||||
m_goalValue = 0.0f;
|
||||
|
|
|
|||
|
|
@ -222,7 +222,12 @@ bool BotSupport::isDoorEntity (edict_t *ent) {
|
|||
if (game.isNullEntity (ent)) {
|
||||
return false;
|
||||
}
|
||||
return ent->v.classname.str ().startsWith ("func_door");
|
||||
const auto classHash = ent->v.classname.str ().hash ();
|
||||
|
||||
constexpr auto kFuncDoor = StringRef::fnv1a32 ("func_door");
|
||||
constexpr auto kFuncDoorRotating = StringRef::fnv1a32 ("func_door_rotating");
|
||||
|
||||
return classHash == kFuncDoor || classHash == kFuncDoorRotating;
|
||||
}
|
||||
|
||||
bool BotSupport::isHostageEntity (edict_t *ent) {
|
||||
|
|
@ -241,6 +246,14 @@ bool BotSupport::isShootableBreakable (edict_t *ent) {
|
|||
if (game.isNullEntity (ent) || ent == game.getStartEntity ()) {
|
||||
return false;
|
||||
}
|
||||
// todo: move the breakables list into own array, and refresh them every round, since next thing is very expensive
|
||||
#if 0
|
||||
StringRef material = engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "material");
|
||||
|
||||
if (material == "7") {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
const auto limit = cv_breakable_health_limit.as <float> ();
|
||||
|
||||
// not shootable
|
||||
|
|
|
|||
|
|
@ -148,6 +148,12 @@ void Bot::normal_ () {
|
|||
campingAllowed = false;
|
||||
}
|
||||
|
||||
// if the bot is about to come to the camp spot, but there is already someone else camping
|
||||
if (!campingAllowed && getTask ()->data == m_currentNodeIndex && getTask ()->data != kInvalidNodeIndex) {
|
||||
clearSearchNodes ();
|
||||
getTask ()->data = kInvalidNodeIndex;
|
||||
}
|
||||
|
||||
if (campingAllowed) {
|
||||
// crouched camping here?
|
||||
if (m_pathFlags & NodeFlag::Crouch) {
|
||||
|
|
@ -427,7 +433,7 @@ void Bot::seekCover_ () {
|
|||
startTask (Task::Hide, TaskPri::Hide, kInvalidNodeIndex, game.time () + rg (3.0f, 12.0f), false);
|
||||
|
||||
// get a valid look direction
|
||||
const Vector &dest = getCampDirection (m_lastEnemyOrigin);
|
||||
const auto &dest = getCampDirection (m_lastEnemyOrigin);
|
||||
|
||||
m_aimFlags |= AimFlags::Camp;
|
||||
m_lookAtSafe = dest;
|
||||
|
|
@ -641,18 +647,18 @@ void Bot::camp_ () {
|
|||
|
||||
// random camp dir, or prediction
|
||||
auto useRandomCampDirOrPredictEnemy = [&] () {
|
||||
if (!m_lastEnemyOrigin.empty ()) {
|
||||
if (!m_lastEnemyOrigin.empty () && util.isAlive (m_lastEnemy)) {
|
||||
auto pathLength = m_lastPredictLength;
|
||||
auto predictNode = m_lastPredictIndex;
|
||||
|
||||
if (pathLength > 1 && isNodeValidForPredict (predictNode)) {
|
||||
if (isNodeValidForPredict (predictNode) && pathLength > 1) {
|
||||
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
|
||||
}
|
||||
else {
|
||||
pathLength = 0;
|
||||
predictNode = findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||
|
||||
if (pathLength > 1 && isNodeValidForPredict (predictNode)) {
|
||||
if (isNodeValidForPredict (predictNode) && pathLength > 1) {
|
||||
m_lookAtSafe = graph[predictNode].origin + pev->view_ofs;
|
||||
}
|
||||
}
|
||||
|
|
@ -681,7 +687,7 @@ void Bot::camp_ () {
|
|||
TraceResult tr {};
|
||||
|
||||
// and use real angles to check it
|
||||
auto to = m_pathOrigin + dest.forward () * 500.0f;
|
||||
const auto &to = m_pathOrigin + dest.forward () * 500.0f;
|
||||
|
||||
// let's check the destination
|
||||
game.testLine (getEyesPos (), to, TraceIgnore::Monsters, ent (), &tr);
|
||||
|
|
@ -886,7 +892,7 @@ void Bot::defuseBomb_ () {
|
|||
defuseRemainingTime = fullDefuseTime - game.time ();
|
||||
}
|
||||
|
||||
const Vector &bombPos = graph.getBombOrigin ();
|
||||
const auto &bombPos = graph.getBombOrigin ();
|
||||
bool defuseError = false;
|
||||
|
||||
// exception: bomb has been defused
|
||||
|
|
@ -1001,11 +1007,11 @@ void Bot::defuseBomb_ () {
|
|||
botStandOrigin = pev->origin;
|
||||
}
|
||||
|
||||
const float duckLengthSq = m_entity.distanceSq (botDuckOrigin);
|
||||
const float standLengthSq = m_entity.distanceSq (botStandOrigin);
|
||||
const float duckDistanceSq = m_entity.distanceSq (botDuckOrigin);
|
||||
const float standDistanceSq = m_entity.distanceSq (botStandOrigin);
|
||||
|
||||
if (duckLengthSq > cr::sqrf (75.0f) || standLengthSq > cr::sqrf (75.0f)) {
|
||||
if (standLengthSq < duckLengthSq) {
|
||||
if (duckDistanceSq > cr::sqrf (75.0f) || standDistanceSq > cr::sqrf (75.0f)) {
|
||||
if (standDistanceSq < duckDistanceSq) {
|
||||
m_duckDefuse = false; // stand
|
||||
}
|
||||
else {
|
||||
|
|
@ -1126,7 +1132,7 @@ void Bot::followUser_ () {
|
|||
int destIndex = graph.getNearest (m_targetEntity->v.origin);
|
||||
auto points = graph.getNearestInRadius (200.0f, m_targetEntity->v.origin);
|
||||
|
||||
for (auto &newIndex : points) {
|
||||
for (const auto &newIndex : points) {
|
||||
// if node not yet used, assign it as dest
|
||||
if (newIndex != m_currentNodeIndex && !isOccupiedNode (newIndex)) {
|
||||
destIndex = newIndex;
|
||||
|
|
@ -1398,10 +1404,7 @@ void Bot::escapeFromBomb_ () {
|
|||
pev->button |= IN_ATTACK2;
|
||||
}
|
||||
|
||||
if (!usesKnife ()
|
||||
&& m_lastEnemyOrigin.empty ()
|
||||
&& !(m_states & Sense::SeeingEnemy)
|
||||
&& !util.isAlive (m_lastEnemy)) {
|
||||
if (!usesKnife () && game.isNullEntity (m_enemy) && !util.isAlive (m_lastEnemy)) {
|
||||
selectWeaponById (Weapon::Knife);
|
||||
}
|
||||
|
||||
|
|
@ -1457,7 +1460,6 @@ void Bot::escapeFromBomb_ () {
|
|||
}
|
||||
|
||||
void Bot::shootBreakable_ () {
|
||||
|
||||
// breakable destroyed?
|
||||
if (!util.isShootableBreakable (m_breakableEntity)) {
|
||||
completeTask ();
|
||||
|
|
@ -1471,12 +1473,12 @@ void Bot::shootBreakable_ () {
|
|||
m_lookAtSafe = m_breakableOrigin;
|
||||
|
||||
// is bot facing the breakable?
|
||||
if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.95f && util.isVisible (m_breakableOrigin, ent ())) {
|
||||
m_aimFlags |= AimFlags::Override;
|
||||
|
||||
if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.90f) {
|
||||
m_moveSpeed = 0.0f;
|
||||
m_strafeSpeed = 0.0f;
|
||||
|
||||
m_aimFlags |= AimFlags::Override;
|
||||
|
||||
if (usesKnife ()) {
|
||||
selectBestWeapon ();
|
||||
}
|
||||
|
|
@ -1501,7 +1503,7 @@ void Bot::pickupItem_ () {
|
|||
|
||||
return;
|
||||
}
|
||||
const Vector &dest = game.getEntityOrigin (m_pickupItem);
|
||||
const auto &dest = game.getEntityOrigin (m_pickupItem);
|
||||
|
||||
m_destOrigin = dest;
|
||||
m_entity = dest;
|
||||
|
|
@ -1669,7 +1671,7 @@ void Bot::pickupItem_ () {
|
|||
}
|
||||
|
||||
// check if hostage is with a human teammate (hack)
|
||||
for (auto &client : util.getClients ()) {
|
||||
for (const auto &client : util.getClients ()) {
|
||||
if ((client.flags & ClientFlags::Used) && !(client.ent->v.flags & FL_FAKECLIENT) && (client.flags & ClientFlags::Alive) &&
|
||||
client.team == m_team && client.ent->v.origin.distanceSq (ent->v.origin) <= cr::sqrf (240.0f)) {
|
||||
return EntitySearchResult::Continue;
|
||||
|
|
|
|||
|
|
@ -361,9 +361,9 @@ bool Frustum::isObjectInsidePlane (const Plane &plane, const Vector ¢er, flo
|
|||
return plane.result + (plane.normal | point) >= 0.0f;
|
||||
};
|
||||
|
||||
const Vector &test = plane.normal.get2d ();
|
||||
const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
|
||||
const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
|
||||
const auto &test = plane.normal.get2d ();
|
||||
const auto &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
|
||||
const auto &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
|
||||
|
||||
return isPointInsidePlane (top) || isPointInsidePlane (bottom);
|
||||
}
|
||||
|
|
@ -403,7 +403,7 @@ void Frustum::calculate (Planes &planes, const Vector &viewAngle, const Vector &
|
|||
|
||||
bool Frustum::check (const Planes &planes, edict_t *ent) const {
|
||||
constexpr auto kOffset = Vector (0.0f, 0.0f, 5.0f);
|
||||
const Vector &origin = ent->v.origin - kOffset;
|
||||
const auto &origin = ent->v.origin - kOffset;
|
||||
|
||||
for (const auto &plane : planes) {
|
||||
if (!isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) {
|
||||
|
|
@ -507,26 +507,25 @@ void Bot::setAimDirection () {
|
|||
auto predictNode = m_lastPredictIndex;
|
||||
|
||||
auto isPredictedIndexApplicable = [&] () -> bool {
|
||||
if (!isNodeValidForPredict (predictNode)
|
||||
|| pathLength >= cv_max_nodes_for_predict.as <int> ()) {
|
||||
TraceResult result {};
|
||||
game.testLine (getEyesPos (), graph[predictNode].origin + pev->view_ofs, TraceIgnore::None, ent (), &result);
|
||||
|
||||
if (result.flFraction < 0.5f) {
|
||||
return false;
|
||||
}
|
||||
const float distToPredictNodeSq = graph[predictNode].origin.distanceSq (pev->origin);
|
||||
|
||||
if (distToPredictNodeSq >= cr::sqrf (2048.0f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vistab.visible (m_currentNodeIndex, predictNode)
|
||||
|| !vistab.visible (m_previousNodes[0], predictNode)
|
||||
|| !vistab.visible (predictNode, m_currentNodeIndex)) {
|
||||
|
||||
if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) {
|
||||
predictNode = kInvalidNodeIndex;
|
||||
pathLength = kInfiniteDistanceLong;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TraceResult result {};
|
||||
game.testLine (getEyesPos (), graph[predictNode].origin + pev->view_ofs, TraceIgnore::None, ent (), &result);
|
||||
|
||||
return result.flFraction >= 0.8f && graph[predictNode].origin.distanceSq (pev->origin) > cr::sqrf (256.0f);
|
||||
return isNodeValidForPredict (predictNode) && pathLength < cv_max_nodes_for_predict.as <int> ();
|
||||
};
|
||||
|
||||
if (changePredictedEnemy) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue