nav: send terrorists directly guard planted bomp

aim: various fixes and tweaks
bot: removed yb_whose_your_daddy cvar
This commit is contained in:
jeefo 2023-04-04 16:53:01 +03:00
commit 722e4eda93
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
5 changed files with 269 additions and 256 deletions

View file

@ -737,7 +737,7 @@ private:
Vector m_enemyOrigin {}; // target origin chosen for shooting Vector m_enemyOrigin {}; // target origin chosen for shooting
Vector m_grenade {}; // calculated vector for grenades Vector m_grenade {}; // calculated vector for grenades
Vector m_entity {}; // origin of entities like buttons etc. Vector m_entity {}; // origin of entities like buttons etc.
Vector m_camp {}; // aiming vector when camping. Vector m_lookAtSafe {}; // aiming vector when camping.
Vector m_desiredVelocity {}; // desired velocity for jump nodes Vector m_desiredVelocity {}; // desired velocity for jump nodes
Vector m_breakableOrigin {}; // origin of breakable Vector m_breakableOrigin {}; // origin of breakable
@ -754,12 +754,13 @@ private:
private: private:
int pickBestWeapon (int *vec, int count, int moneySave); int pickBestWeapon (int *vec, int count, int moneySave);
int findCampingDirection (); int findCampingDirection ();
int findAimingNode (const Vector &to); int findAimingNode (const Vector &to, int &pathLength);
int findNearestNode (); int findNearestNode ();
int findBombNode (); int findBombNode ();
int findCoverNode (float maxDistance); int findCoverNode (float maxDistance);
int findDefendNode (const Vector &origin); int findDefendNode (const Vector &origin);
int findBestGoal (); int findBestGoal ();
int findBestGoalWhenBombAction ();
int findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive); int findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive);
int bestPrimaryCarried (); int bestPrimaryCarried ();
int bestSecondaryCarried (); int bestSecondaryCarried ();
@ -1181,7 +1182,6 @@ extern ConVar cv_chat;
extern ConVar cv_language; extern ConVar cv_language;
extern ConVar cv_show_latency; extern ConVar cv_show_latency;
extern ConVar cv_enable_query_hook; extern ConVar cv_enable_query_hook;
extern ConVar cv_whose_your_daddy;
extern ConVar cv_chatter_path; extern ConVar cv_chatter_path;
extern ConVar cv_quota; extern ConVar cv_quota;
extern ConVar cv_difficulty; extern ConVar cv_difficulty;
@ -1202,6 +1202,7 @@ extern ConVar mp_limitteams;
extern ConVar mp_autoteambalance; extern ConVar mp_autoteambalance;
extern ConVar mp_footsteps; extern ConVar mp_footsteps;
extern ConVar mp_startmoney; extern ConVar mp_startmoney;
extern ConVar mp_c4timer;
// execute client command helper // execute client command helper
template <typename ...Args> void Bot::issueCommand (const char *fmt, Args &&...args) { template <typename ...Args> void Bot::issueCommand (const char *fmt, Args &&...args) {

View file

@ -502,14 +502,6 @@ edict_t *Bot::lookupBreakable () {
} }
void Bot::setIdealReactionTimers (bool actual) { void Bot::setIdealReactionTimers (bool actual) {
// zero out reaction times for extreme mode
if (cv_whose_your_daddy.bool_ ()) {
m_idealReactionTime = 0.05f;
m_actualReactionTime = 0.095f;
return;
}
const auto tweak = conf.getDifficultyTweaks (m_difficulty); const auto tweak = conf.getDifficultyTweaks (m_difficulty);
if (actual) { if (actual) {
@ -1886,8 +1878,17 @@ void Bot::setConditions () {
m_states &= ~Sense::HearingEnemy; m_states &= ~Sense::HearingEnemy;
} }
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty () && m_seeEnemyTime + 0.5f < game.time ()) {
auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
if (distanceToLastEnemySq < cr::square (1600.0f)) {
auto pathLength = 0;
auto nodeIndex = findAimingNode (m_lastEnemyOrigin, pathLength);
if (graph.exists (nodeIndex) && pathLength < kMaxBucketsInsidePos * 2 && pev->origin.distanceSq (graph[nodeIndex].origin) > cr::square (384.0f)) {
m_aimFlags |= AimFlags::PredictPath; m_aimFlags |= AimFlags::PredictPath;
}
}
if (seesEntity (m_lastEnemyOrigin, true)) { if (seesEntity (m_lastEnemyOrigin, true)) {
m_aimFlags |= AimFlags::LastEnemy; m_aimFlags |= AimFlags::LastEnemy;
@ -1985,7 +1986,7 @@ void Bot::filterTasks () {
ratio = timeHeard * 0.1f; ratio = timeHeard * 0.1f;
} }
bool lowAmmo = isLowOnAmmo (m_currentWeapon, 0.18f); bool lowAmmo = isLowOnAmmo (m_currentWeapon, 0.18f);
bool sniping = m_sniperStopTime <= game.time () && lowAmmo; bool sniping = m_sniperStopTime > game.time () && lowAmmo;
if (bots.isBombPlanted () || m_isStuck || usesKnife ()) { if (bots.isBombPlanted () || m_isStuck || usesKnife ()) {
ratio /= 3.0f; // reduce the seek cover desire if bomb is planted ratio /= 3.0f; // reduce the seek cover desire if bomb is planted
@ -2080,7 +2081,7 @@ void Bot::filterTasks () {
auto survive = thresholdDesire (&filter[Task::SeekCover], 40.0f, 0.0f); auto survive = thresholdDesire (&filter[Task::SeekCover], 40.0f, 0.0f);
survive = subsumeDesire (&filter[Task::Hide], survive); survive = subsumeDesire (&filter[Task::Hide], survive);
auto def = thresholdDesire (&filter[Task::Hunt], 41.0f, 0.0f); // don't allow hunting if desires 60< auto def = thresholdDesire (&filter[Task::Hunt], 60.0f, 0.0f); // don't allow hunting if desires 60<
offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff
auto sub = maxDesire (offensive, def); // default normal & careful tasks against offensive actions auto sub = maxDesire (offensive, def); // default normal & careful tasks against offensive actions
@ -2771,7 +2772,7 @@ void Bot::updateAimDir () {
} }
if (flags & AimFlags::Override) { if (flags & AimFlags::Override) {
m_lookAt = m_camp; m_lookAt = m_lookAtSafe;
} }
else if (flags & AimFlags::Grenade) { else if (flags & AimFlags::Grenade) {
m_lookAt = m_throw; m_lookAt = m_throw;
@ -2802,6 +2803,9 @@ void Bot::updateAimDir () {
if (m_pickupType == Pickup::Hostage) { if (m_pickupType == Pickup::Hostage) {
m_lookAt.z += 48.0f; m_lookAt.z += 48.0f;
} }
else if (m_pickupType == Pickup::Weapon) {
m_lookAt.z += 72.0;
}
} }
else if (flags & AimFlags::LastEnemy) { else if (flags & AimFlags::LastEnemy) {
m_lookAt = m_lastEnemyOrigin; m_lookAt = m_lastEnemyOrigin;
@ -2814,100 +2818,59 @@ void Bot::updateAimDir () {
m_wantsToFire = true; m_wantsToFire = true;
} }
} }
else {
auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
auto maxDistanceToEnemySq = cr::square (1600.0f);
if (distanceToLastEnemySq >= maxDistanceToEnemySq) {
m_lastEnemyOrigin = nullptr;
m_aimFlags &= ~AimFlags::LastEnemy;
}
}
} }
else if (flags & AimFlags::PredictPath) { else if (flags & AimFlags::PredictPath) {
TraceResult tr {}; bool changePredictedEnemy = true;
auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); if (m_timeNextTracking > game.time () && m_trackingEdict == m_lastEnemy) {
auto maxDistanceToEnemySq = cr::square (1600.0f);
if ((distanceToLastEnemySq < maxDistanceToEnemySq || usesSniper ()) && util.isAlive (m_lastEnemy)) {
game.testLine (getEyesPos (), m_lastEnemyOrigin, TraceIgnore::Glass, pev->pContainingEntity, &tr);
if (tr.flFraction >= 0.2f || tr.pHit != game.getStartEntity ()) {
auto changePredictedEnemy = true;
if (m_timeNextTracking < game.time () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) {
changePredictedEnemy = false; changePredictedEnemy = false;
} }
if (changePredictedEnemy) { if (changePredictedEnemy) {
auto aimPoint = findAimingNode (m_lastEnemyOrigin); auto pathLength = 0;
auto aimNode = findAimingNode (m_lastEnemyOrigin, pathLength);
if (aimPoint != kInvalidNodeIndex) { if (graph.exists (aimNode) && pathLength < kMaxBucketsInsidePos * 2) {
m_lookAt = graph[aimPoint].origin + pev->view_ofs; m_lookAt = graph[aimNode].origin;
m_camp = m_lookAt; m_lookAtSafe = m_lookAt;
m_timeNextTracking = game.time () + 0.5f; m_timeNextTracking = game.time () + 0.5f;
m_trackingEdict = m_lastEnemy; m_trackingEdict = m_lastEnemy;
if (!usesSniper () && lastEnemyShootable ()) {
m_wantsToFire = true;
}
} }
else { else {
m_lookAt = m_camp; if (!m_lookAtSafe.empty ()) {
} m_lookAt = m_lookAtSafe;
}
else {
m_lookAt = m_camp;
}
}
else {
m_aimFlags &= ~AimFlags::PredictPath; // forget enemy far away
if (distanceToLastEnemySq >= maxDistanceToEnemySq) {
m_lastEnemyOrigin = nullptr;
} }
} }
} }
else { else {
m_aimFlags &= ~AimFlags::PredictPath; // forget enemy far away m_lookAt = m_lookAtSafe;
if (distanceToLastEnemySq >= maxDistanceToEnemySq) {
m_lastEnemyOrigin = nullptr;
}
} }
} }
else if (flags & AimFlags::Camp) { else if (flags & AimFlags::Camp) {
m_lookAt = m_camp; m_lookAt = m_lookAtSafe;
} }
else if (flags & AimFlags::Nav) { else if (flags & AimFlags::Nav) {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
auto smoothView = [&] (int32 index) -> Vector { if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::square (240.0f)) {
auto radius = graph[index].radius;
if (radius > 0.0f) {
return Vector (pev->angles.x, cr::wrapAngle (pev->angles.y + rg.get (-90.0f, 90.0f)), 0.0f).forward () * rg.get (2.0f, 4.0f);
}
return nullptr;
};
if (m_moveToGoal && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(pev->button & IN_DUCK) && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & (NodeFlag::Ladder | NodeFlag::Crouch)) && m_pathWalk.hasNext () && pev->origin.distanceSq (m_destOrigin) < cr::square (160.0f)) {
auto nextPathIndex = m_pathWalk.next (); auto nextPathIndex = m_pathWalk.next ();
if (graph.isVisible (m_currentNodeIndex, nextPathIndex)) { if (graph.isVisible (m_currentNodeIndex, nextPathIndex)) {
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs + smoothView (nextPathIndex); m_lookAt = graph[nextPathIndex].origin;
} }
else { else {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
} }
} }
else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()){
m_lookAt = m_lastEnemyOrigin;;
}
else { else {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
} }
if (m_canChooseAimDirection && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & NodeFlag::Ladder)) { if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & NodeFlag::Ladder)) {
auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex); auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
if (graph.exists (dangerIndex) && graph.isVisible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) { if (graph.exists (dangerIndex) && graph.isVisible (m_currentNodeIndex, dangerIndex) && !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
@ -2915,13 +2878,18 @@ void Bot::updateAimDir () {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
} }
else { else {
m_lookAt = graph[dangerIndex].origin + pev->view_ofs + smoothView (dangerIndex); m_lookAt = graph[dangerIndex].origin + pev->view_ofs;
// add danger flags // add danger flags
m_aimFlags |= AimFlags::Danger; m_aimFlags |= AimFlags::Danger;
} }
} }
} }
// aloways use our z
if (m_lookAt == m_destOrigin) {
m_lookAt.z = getEyesPos ().z;
}
} }
if (m_lookAt.empty ()) { if (m_lookAt.empty ()) {
@ -3028,12 +2996,6 @@ void Bot::frame () {
kick (); kick ();
return; return;
} }
// clear enemy far away
if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::square (1600.0f)) {
m_lastEnemy = nullptr;
m_lastEnemyOrigin = nullptr;
}
m_slowFrameTimestamp = game.time () + 0.5f; m_slowFrameTimestamp = game.time () + 0.5f;
} }
@ -3227,7 +3189,7 @@ void Bot::normal_ () {
m_timeCamping = game.time () + rg.get (cv_camping_time_min.float_ (), cv_camping_time_max.float_ ()); m_timeCamping = game.time () + rg.get (cv_camping_time_min.float_ (), cv_camping_time_max.float_ ());
startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, m_timeCamping, true); startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, m_timeCamping, true);
m_camp = m_path->origin + m_path->start.forward () * 500.0f; m_lookAtSafe = m_path->origin + m_path->start.forward () * 500.0f;
m_aimFlags |= AimFlags::Camp; m_aimFlags |= AimFlags::Camp;
m_campDirection = 0; m_campDirection = 0;
@ -3507,7 +3469,7 @@ void Bot::seekCover_ () {
getCampDirection (&dest); getCampDirection (&dest);
m_aimFlags |= AimFlags::Camp; m_aimFlags |= AimFlags::Camp;
m_camp = dest; m_lookAtSafe = dest;
m_campDirection = 0; m_campDirection = 0;
// chosen waypoint is a camp waypoint? // chosen waypoint is a camp waypoint?
@ -3609,7 +3571,7 @@ void Bot::pause_ () {
if (m_moveSpeed < -pev->maxspeed) { if (m_moveSpeed < -pev->maxspeed) {
m_moveSpeed = -pev->maxspeed; m_moveSpeed = -pev->maxspeed;
} }
m_camp = getEyesPos () + pev->v_angle.forward () * 500.0f; m_lookAtSafe = getEyesPos () + pev->v_angle.forward () * 500.0f;
m_aimFlags |= AimFlags::Override; m_aimFlags |= AimFlags::Override;
m_wantsToFire = true; m_wantsToFire = true;
@ -3757,22 +3719,23 @@ void Bot::camp_ () {
} }
if (--numFoundPoints >= 0) { if (--numFoundPoints >= 0) {
m_camp = graph[campPoints[rg.get (0, numFoundPoints)]].origin; m_lookAtSafe = graph[campPoints[rg.get (0, numFoundPoints)]].origin;
} }
else { else {
m_camp = graph[findCampingDirection ()].origin; m_lookAtSafe = graph[findCampingDirection ()].origin;
} }
} }
else { else {
if ((!game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) || util.isAlive (m_lastEnemy)) { if ((!game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) || util.isAlive (m_lastEnemy)) {
auto lastEnemyNearestIndex = findAimingNode (m_lastEnemyOrigin); auto pathLength = 0;
auto lastEnemyNearestIndex = findAimingNode (m_lastEnemyOrigin, pathLength);
if (lastEnemyNearestIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, lastEnemyNearestIndex)) { if (pathLength > 0 && graph.exists (lastEnemyNearestIndex)) {
m_camp = graph[lastEnemyNearestIndex].origin; m_lookAtSafe = graph[lastEnemyNearestIndex].origin;
} }
} }
else { else {
m_camp = graph[findCampingDirection ()].origin; m_lookAtSafe = graph[findCampingDirection ()].origin;
} }
} }
} }
@ -4551,7 +4514,7 @@ void Bot::shootBreakable_ () {
m_checkTerrain = false; m_checkTerrain = false;
m_moveToGoal = false; m_moveToGoal = false;
m_navTimeset = game.time (); m_navTimeset = game.time ();
m_camp = m_breakableOrigin; m_lookAtSafe = m_breakableOrigin;
// is bot facing the breakable? // is bot facing the breakable?
if (util.getShootingCone (ent (), m_breakableOrigin) >= 0.90f) { if (util.getShootingCone (ent (), m_breakableOrigin) >= 0.90f) {

View file

@ -197,7 +197,7 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) {
return false; return false;
} }
if (cv_whose_your_daddy.bool_ () && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) { if (m_difficulty == Difficulty::Expert && m_kpdRatio < 1.5f && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) {
ignoreFOV = true; ignoreFOV = true;
} }
@ -276,7 +276,7 @@ bool Bot::lookupEnemies () {
float scaleFactor = (1.0f / calculateScaleFactor (intresting)); float scaleFactor = (1.0f / calculateScaleFactor (intresting));
float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor; float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor;
if (distance * 0.7f < nearestDistance) { if (distance < nearestDistance) {
nearestDistance = distance; nearestDistance = distance;
newEnemy = intresting; newEnemy = intresting;
} }
@ -297,7 +297,7 @@ bool Bot::lookupEnemies () {
} }
// extra skill player can see thru smoke... if beeing attacked // extra skill player can see thru smoke... if beeing attacked
if ((player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance && cv_whose_your_daddy.bool_ ()) { if ((player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance && m_difficulty == Difficulty::Expert) {
nearestDistance = cr::square (m_maxViewDistance); nearestDistance = cr::square (m_maxViewDistance);
} }
@ -309,7 +309,7 @@ bool Bot::lookupEnemies () {
} }
float distance = player->v.origin.distanceSq (pev->origin); float distance = player->v.origin.distanceSq (pev->origin);
if (distance * 0.7f < nearestDistance) { if (distance < nearestDistance) {
nearestDistance = distance; nearestDistance = distance;
newEnemy = player; newEnemy = player;
@ -350,7 +350,7 @@ bool Bot::lookupEnemies () {
} }
m_targetEntity = nullptr; // stop following when we see an enemy... m_targetEntity = nullptr; // stop following when we see an enemy...
if (cv_whose_your_daddy.bool_ ()) { if (m_difficulty == Difficulty::Expert) {
m_enemySurpriseTime = m_actualReactionTime * 0.5f; m_enemySurpriseTime = m_actualReactionTime * 0.5f;
} }
else { else {
@ -403,7 +403,7 @@ bool Bot::lookupEnemies () {
// shoot at dying players if no new enemy to give some more human-like illusion // shoot at dying players if no new enemy to give some more human-like illusion
if (m_seeEnemyTime + 0.1f > game.time ()) { if (m_seeEnemyTime + 0.1f > game.time ()) {
if (!usesSniper ()) { if (!usesSniper ()) {
m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.45f, 0.60f); m_shootAtDeadTime = game.time () + cr::clamp (m_agressionLevel * 1.25f, 0.25f, 0.45f);
m_actualReactionTime = 0.0f; m_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
@ -493,10 +493,13 @@ const Vector &Bot::getEnemyBodyOffset () {
else if (distance < 800.0f && usesSniper ()) { else if (distance < 800.0f && usesSniper ()) {
m_enemyParts &= ~Visibility::Head; m_enemyParts &= ~Visibility::Head;
} }
else if (distance < kSprayDistance / 2 && !usesPistol ()) {
m_enemyParts &= ~Visibility::Head;
}
Vector aimPos = m_enemy->v.origin; Vector aimPos = m_enemy->v.origin;
if (m_difficulty > Difficulty::Normal) { if (m_difficulty > Difficulty::Normal) {
aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.25f); aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 2.0f);
} }
// if we only suspect an enemy behind a wall take the worst skill // if we only suspect an enemy behind a wall take the worst skill
@ -504,12 +507,12 @@ const Vector &Bot::getEnemyBodyOffset () {
aimPos += getBodyOffsetError (distance); aimPos += getBodyOffsetError (distance);
} }
else if (util.isPlayer (m_enemy)) { else if (util.isPlayer (m_enemy)) {
const float highOffset = cv_whose_your_daddy.bool_ () ? 1.5f : 0.0f; const float highOffset = m_kpdRatio < 1.0f ? 1.5f : 0.0f;
// now take in account different parts of enemy body // now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) { if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct; auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
auto onLoosingStreak = m_fearLevel > m_agressionLevel || m_kpdRatio < 1.5f; auto onLoosingStreak = (m_fearLevel > m_agressionLevel || m_kpdRatio < 1.25f);
// reduce headshot percent in case we're play too good // reduce headshot percent in case we're play too good
if (!onLoosingStreak) { if (!onLoosingStreak) {
@ -518,7 +521,7 @@ const Vector &Bot::getEnemyBodyOffset () {
// now check is our skill match to aim at head, else aim at enemy body // now check is our skill match to aim at head, else aim at enemy body
if (rg.chance (headshotPct)) { if (rg.chance (headshotPct)) {
if (onLoosingStreak || cv_whose_your_daddy.bool_ ()) { if (onLoosingStreak) {
aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance);
} }
else { else {
@ -558,13 +561,13 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
static float offsetRanges[9][3] = { static float offsetRanges[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
{ 2.5f, 1.5f, 0.2f }, // pistol { 1.5f, 1.5f, -1.2f }, // pistol
{ 6.5f, 0.0f, -9.9f }, // shotgun { 6.5f, 0.0f, -9.9f }, // shotgun
{ 0.5f, -6.5f, -9.0f }, // zoomrifle { 0.5f, -8.5f, -11.0f }, // zoomrifle
{ 0.5f, -6.5f, -9.5f }, // rifle { 0.5f, -8.5f, -11.5f }, // rifle
{ 2.5f, 0.5f, -4.5f }, // smg { 2.5f, 0.5f, -4.5f }, // smg
{ 0.5f, 0.5f, 1.5f }, // sniper { 0.5f, 0.5f, 1.5f }, // sniper
{ 1.5f, -2.0f, -9.0f } // heavy { 1.5f, -2.0f, -12.0f } // heavy
}; };
// only highskilled bots do that // only highskilled bots do that
@ -769,16 +772,16 @@ bool Bot::needToPauseFiring (float distance) {
return true; return true;
} }
} }
float offset = 5.0f; float offset = 4.25f;
if (distance < kSprayDistance * 0.5f) { if (distance < kSprayDistance / 4) {
return false; return false;
} }
else if (distance < kSprayDistance) { else if (distance < kSprayDistance) {
offset = 12.0f; offset = 10.0f;
} }
else if (distance < kDoubleSprayDistance) { else if (distance < kDoubleSprayDistance) {
offset = 10.0f; offset = 8.0f;
} }
const float xPunch = cr::deg2rad (pev->punchangle.x); const float xPunch = cr::deg2rad (pev->punchangle.x);
const float yPunch = cr::deg2rad (pev->punchangle.y); const float yPunch = cr::deg2rad (pev->punchangle.y);
@ -1117,10 +1120,13 @@ void Bot::attackMovement () {
if (game.isNullEntity (m_enemy)) { if (game.isNullEntity (m_enemy)) {
return; return;
} }
float distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum?
if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.time ()) { if (m_lastUsedNodesTime - getFrameInterval () > game.time ()) {
int approach; return;
}
auto approach = 0;
auto distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum?
if (usesKnife ()) { if (usesKnife ()) {
approach = 100; approach = 100;
@ -1155,12 +1161,11 @@ void Bot::attackMovement () {
m_moveSpeed = -pev->maxspeed; m_moveSpeed = -pev->maxspeed;
} }
if (m_lastFightStyleCheck + 3.0f < game.time ()) {
if (usesSniper ()) { if (usesSniper ()) {
m_fightStyle = Fight::Stay; m_fightStyle = Fight::Stay;
m_lastFightStyleCheck = game.time ();
} }
else if (usesRifle () || usesSubmachine ()) { else if (usesRifle () || usesSubmachine () || usesHeavy ()) {
if (m_lastFightStyleCheck + 3.0f < game.time ()) {
int rand = rg.get (1, 100); int rand = rg.get (1, 100);
if (distance < 500.0f) { if (distance < 500.0f) {
@ -1182,18 +1187,17 @@ void Bot::attackMovement () {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
} }
} }
}
else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75)) {
m_fightStyle = Fight::Strafe;
}
else {
m_fightStyle = Fight::Stay;
}
m_lastFightStyleCheck = game.time (); m_lastFightStyleCheck = game.time ();
} }
}
else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 100)) {
m_fightStyle = Fight::Strafe;
}
if (isInViewCone (m_enemy->v.origin) && usesKnife ()) { if (m_fightStyle == Fight::Strafe) {
m_fightStyle = Fight::Strafe;
}
if ((m_difficulty >= Difficulty::Normal && m_fightStyle == Fight::Strafe) || ((pev->button & IN_RELOAD) || m_isReloading) || (usesPistol () && distance < 768.0f) || usesKnife ()) {
if (m_strafeSetTime < game.time ()) { if (m_strafeSetTime < game.time ()) {
// to start strafing, we have to first figure out if the target is on the left side or right side // to start strafing, we have to first figure out if the target is on the left side or right side
@ -1210,7 +1214,7 @@ void Bot::attackMovement () {
if (rg.chance (30)) { if (rg.chance (30)) {
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);
} }
m_strafeSetTime = game.time () + rg.get (1.0f, 3.0f); m_strafeSetTime = game.time () + rg.get (1.2f, 2.4f);
} }
if (m_combatStrafeDir == Dodge::Right) { if (m_combatStrafeDir == Dodge::Right) {
@ -1219,7 +1223,7 @@ void Bot::attackMovement () {
} }
else { else {
m_combatStrafeDir = Dodge::Left; m_combatStrafeDir = Dodge::Left;
m_strafeSetTime = game.time () + rg.get (1.0f, 1.5f); m_strafeSetTime = game.time () + rg.get (0.8f, 1.2f);
} }
} }
else { else {
@ -1228,7 +1232,7 @@ void Bot::attackMovement () {
} }
else { else {
m_combatStrafeDir = Dodge::Right; m_combatStrafeDir = Dodge::Right;
m_strafeSetTime = game.time () + rg.get (1.0f, 1.5f); m_strafeSetTime = game.time () + rg.get (0.8f, 1.2f);
} }
} }
@ -1248,11 +1252,10 @@ void Bot::attackMovement () {
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;
m_navTimeset = game.time (); m_navTimeset = game.time ();
} }
}
if (m_difficulty >= Difficulty::Hard && isOnFloor () && (m_duckTime < game.time ())) { if (m_difficulty >= Difficulty::Hard && isOnFloor () && (m_duckTime < game.time ())) {
if (distance < 768.0f) { if (distance < 768.0f) {
if (rg.get (0, 1000) < rg.get (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) { if (rg.get (0, 1000) < rg.get (7, 12) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
pev->button |= IN_JUMP; pev->button |= IN_JUMP;
} }
} }

View file

@ -912,10 +912,6 @@ void BotManager::updateBotDifficulties () {
} }
void BotManager::balanceBotDifficulties () { void BotManager::balanceBotDifficulties () {
// with nightmare difficulty, there is no balance
if (cv_whose_your_daddy.bool_ ()) {
return;
}
// difficulty chaning once per round (time) // difficulty chaning once per round (time)
auto updateDifficulty = [] (Bot *bot, int32 offset) { auto updateDifficulty = [] (Bot *bot, int32 offset) {
bot->m_difficulty = cr::clamp (static_cast <Difficulty> (bot->m_difficulty + offset), Difficulty::Noob, Difficulty::Expert); bot->m_difficulty = cr::clamp (static_cast <Difficulty> (bot->m_difficulty + offset), Difficulty::Noob, Difficulty::Expert);

View file

@ -7,42 +7,22 @@
#include <yapb.h> #include <yapb.h>
ConVar cv_whose_your_daddy ("yb_whose_your_daddy", "0", "Enables or disables extra hard difficulty for bots.");
ConVar cv_path_heuristic_mode ("yb_path_heuristic_mode", "4", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f); ConVar cv_path_heuristic_mode ("yb_path_heuristic_mode", "4", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f);
ConVar cv_path_danger_factor_min ("yb_path_danger_factor_min", "200", "Lower bound of danger factor that used to add additional danger to path based on practice.", true, 100.0f, 2400.0f); ConVar cv_path_danger_factor_min ("yb_path_danger_factor_min", "200", "Lower bound of danger factor that used to add additional danger to path based on practice.", true, 100.0f, 2400.0f);
ConVar cv_path_danger_factor_max ("yb_path_danger_factor_max", "400", "Upper bound of danger factor that used to add additional danger to path based on practice.", true, 200.0f, 4800.0f); ConVar cv_path_danger_factor_max ("yb_path_danger_factor_max", "400", "Upper bound of danger factor that used to add additional danger to path based on practice.", true, 200.0f, 4800.0f);
int Bot::findBestGoal () { int Bot::findBestGoal () {
auto pushToHistroy = [&] (int32 goal) -> int32 { auto pushToHistroy = [&] (int32 goal) -> int32 {
m_goalHistory.push (goal); m_goalHistory.push (goal);
return goal; return goal;
}; };
// chooses a destination (goal) node for a bot // chooses a destination (goal) node for a bot
if (!bots.isBombPlanted () && m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) {
int result = kInvalidNodeIndex; auto result = findBestGoalWhenBombAction ();
game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) {
if (util.isModel (ent, "backpack.mdl")) {
result = graph.getNearest (game.getEntityOrigin (ent));
if (graph.exists (result)) { if (graph.exists (result)) {
return EntitySearchResult::Break; return result;
}
}
return EntitySearchResult::Continue;
});
// found one ?
if (graph.exists (result)) {
return m_loosedBombNodeIndex = result;
}
// forcing terrorist bot to not move to another bomb spot
if (m_inBombZone && !m_hasProgressBar && m_hasC4) {
return graph.getNearest (pev->origin, 768.0f, NodeFlag::Goal);
} }
} }
int tactic = 0; int tactic = 0;
@ -165,6 +145,68 @@ int Bot::findBestGoal () {
return pushToHistroy (findGoalPost (tactic, defensiveNodes, offensiveNodes)); return pushToHistroy (findGoalPost (tactic, defensiveNodes, offensiveNodes));
} }
int Bot::findBestGoalWhenBombAction () {
int result = kInvalidNodeIndex;
if (!bots.isBombPlanted ()) {
game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) {
if (util.isModel (ent, "backpack.mdl")) {
result = graph.getNearest (game.getEntityOrigin (ent));
if (graph.exists (result)) {
return EntitySearchResult::Break;
}
}
return EntitySearchResult::Continue;
});
// found one ?
if (graph.exists (result)) {
return m_loosedBombNodeIndex = result;
}
// forcing terrorist bot to not move to another bomb spot
if (m_inBombZone && !m_hasProgressBar && m_hasC4) {
return graph.getNearest (pev->origin, 1024.0f, NodeFlag::Goal);
}
}
else if (!m_defendedBomb) {
const auto &bombOrigin = graph.getBombOrigin ();
if (!bombOrigin.empty ()) {
m_defendedBomb = true;
result = findDefendNode (bombOrigin);
const Path &path = graph[result];
float bombTimer = mp_c4timer.float_ ();
float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin);
if (timeMidBlowup > game.time ()) {
clearTask (Task::MoveToPosition); // remove any move tasks
startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, timeMidBlowup, true); // push camp task on to stack
startTask (Task::MoveToPosition, TaskPri::MoveToPosition, result, timeMidBlowup, true); // push move command
if (path.vis.crouch <= path.vis.stand) {
m_campButtons |= IN_DUCK;
}
else {
m_campButtons &= ~IN_DUCK;
}
if (rg.chance (90)) {
pushChatterMessage (Chatter::DefendingBombsite);
}
}
else {
pushRadioMessage (Radio::ShesGonnaBlow); // issue an additional radio message
}
return result;
}
}
return result;
}
int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) { int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) {
int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex };
@ -349,6 +391,11 @@ bool Bot::doPlayerAvoidance (const Vector &normal) {
continue; continue;
} }
// skip if it's not standing
if (client.ent->v.flags & FL_DUCKING) {
continue;
}
// our team, alive and not myself? // our team, alive and not myself?
if (client.team != m_team || client.ent == ent ()) { if (client.team != m_team || client.ent == ent ()) {
continue; continue;
@ -882,7 +929,7 @@ bool Bot::updateNavigation () {
} }
float desiredDistance = 8.0f; float desiredDistance = 8.0f;
float nodeDistance = pev->origin.distance (m_pathOrigin); float nodeDistance = pev->origin.distance2d (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_path->flags & NodeFlag::Lift) { if (m_path->flags & NodeFlag::Lift) {
@ -900,6 +947,9 @@ bool Bot::updateNavigation () {
else if (m_path->number == cv_debug_goal.int_ ()) { else if (m_path->number == cv_debug_goal.int_ ()) {
desiredDistance = 0.0f; desiredDistance = 0.0f;
} }
else if (isOccupiedNode (m_path->number)) {
desiredDistance = 72.0f;
}
else { else {
desiredDistance = m_path->radius; desiredDistance = m_path->radius;
} }
@ -1620,7 +1670,7 @@ void Bot::clearRoute () {
m_routes.clear (); m_routes.clear ();
} }
int Bot::findAimingNode (const Vector &to) { int Bot::findAimingNode (const Vector &to, int &pathLength) {
// return the most distant node which is seen from the bot to the target and is within count // return the most distant node which is seen from the bot to the target and is within count
if (m_currentNodeIndex == kInvalidNodeIndex) { if (m_currentNodeIndex == kInvalidNodeIndex) {
@ -1633,7 +1683,6 @@ int Bot::findAimingNode (const Vector &to) {
if (destIndex == kInvalidNodeIndex) { if (destIndex == kInvalidNodeIndex) {
return kInvalidNodeIndex; return kInvalidNodeIndex;
} }
const float kMaxDistance = ((m_states & Sense::HearingEnemy) || (m_states & Sense::SuspectEnemy) || m_seeEnemyTime + 3.0f > game.time ()) ? 0.0f : 512.0f;
while (destIndex != m_currentNodeIndex) { while (destIndex != m_currentNodeIndex) {
destIndex = (graph.m_matrix.data () + (destIndex * graph.length ()) + m_currentNodeIndex)->index; destIndex = (graph.m_matrix.data () + (destIndex * graph.length ()) + m_currentNodeIndex)->index;
@ -1641,8 +1690,9 @@ int Bot::findAimingNode (const Vector &to) {
if (destIndex < 0) { if (destIndex < 0) {
break; break;
} }
++pathLength;
if (graph.isVisible (m_currentNodeIndex, destIndex) && graph.isVisible (destIndex, m_currentNodeIndex) && kMaxDistance > 0.0f && graph[destIndex].origin.distanceSq (graph[m_currentNodeIndex].origin) > cr::square (kMaxDistance)) { if (graph.isVisible (m_currentNodeIndex, destIndex)) {
bestIndex = destIndex; bestIndex = destIndex;
break; break;
} }
@ -3039,7 +3089,7 @@ void Bot::updateLookAngles () {
return; return;
} }
if (m_difficulty == Difficulty::Expert && (m_aimFlags & AimFlags::Enemy) && (m_wantsToFire || usesSniper ()) && cv_whose_your_daddy.bool_ ()) { if (m_difficulty == Difficulty::Expert && (m_aimFlags & AimFlags::Enemy) && (m_wantsToFire || usesSniper ()) && m_kpdRatio < 1.0f && m_healthValue < 50.0f) {
pev->v_angle = direction; pev->v_angle = direction;
updateBodyAngles (); updateBodyAngles ();