hearing: randomize listen position for heard sounds

graph: restore cvar variables values to initial values when disabling graph editor
cfg: update primary config with actual cvars
vision: remove unnecessary angles clamp
combat: allow to disable engine potential visibility checks
combat: do not aim to head on long distances with AWP, shotguns or with high recoil
bot: set the minimum think fps to 30.0
This commit is contained in:
jeefo 2024-04-11 16:26:12 +03:00
commit cc01693de9
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
11 changed files with 110 additions and 46 deletions

View file

@ -235,6 +235,13 @@ yb_pickup_best "1"
//
yb_ignore_objectives "0"
//
// Affect bot's vision by smoke clouds.
// ---
// Default: "2", Min: "0", Max: "2"
//
yb_smoke_grenade_checks "2"
//
// Enables or disables bots chat functionality.
// ---
@ -252,9 +259,9 @@ yb_chat_percent "30"
//
// Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.
// ---
// Default: "2", Min: "0", Max: "3"
// Default: "1", Min: "0", Max: "3"
//
yb_shoots_thru_walls "2"
yb_shoots_thru_walls "1"
//
// Enables or disables searching world for enemies.
@ -284,6 +291,13 @@ yb_check_enemy_invincibility "0"
//
yb_stab_close_enemies "1"
//
// Use engine to check potential visibility of a enemy.
// ---
// Default: "0", Min: "0", Max: "1"
//
yb_use_engine_pvs_check "0"
//
// Binds specified key for opening bots menu.
// ---
@ -352,6 +366,13 @@ yb_threadpool_workers "-1"
//
yb_grenadier_mode "0"
//
// Make bots ignore enemies for a specified here time in seconds on new round. Useful for Zombie Plague mods.
// ---
// Default: "0", Min: "0", Max: "540"
//
yb_ignore_enemies_after_spawn_time "0"
//
// Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format.
// ---
@ -442,9 +463,9 @@ yb_quota_match "0"
//
// Specifies how many times per second bot code will run.
// ---
// Default: "26.0", Min: "24.0", Max: "90.0"
// Default: "30.0", Min: "24.0", Max: "90.0"
//
yb_think_fps "26.0"
yb_think_fps "30.0"
//
// Specifies amount of time in seconds when bots will be killed if no humans left alive.

View file

@ -427,8 +427,8 @@ namespace TaskPri {
constexpr auto kInfiniteDistance = 9999999.0f;
constexpr auto kInvalidLightLevel = kInfiniteDistance;
constexpr auto kGrenadeCheckTime = 0.6f;
constexpr auto kSprayDistance = 260.0f;
constexpr auto kDoubleSprayDistance = kSprayDistance * 2;
constexpr auto kSprayDistance = 360.0f;
constexpr auto kSprayDistanceX2 = kSprayDistance * 2;
constexpr auto kMaxChatterRepeatInterval = 99.0f;
constexpr auto kViewFrameUpdate = 1.0f / 30.0f;
constexpr auto kGrenadeDamageRadius = 385.0f;

View file

@ -63,6 +63,13 @@ public:
{ }
};
// save old values of changed cvars to revert them back when editing turned off
struct GraphSaveVarValue {
float timelimit {};
float freezetime {};
float roundtime {};
} m_graphSaveVarValues;
private:
StringArray m_args {};
Array <BotCmd> m_cmds {};

View file

@ -790,6 +790,11 @@ 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;
@ -834,6 +839,11 @@ private:
bool usesKnife () const {
return m_weaponType == WeaponType::Melee;
}
// checks if weapon recoil is high
bool isRecoilHigh () const {
return pev->punchangle.x < -1.45f;
}
};
#include "config.h"

View file

@ -1711,6 +1711,7 @@ void Bot::overrideConditions () {
m_isReloading = false;
}
}
if (m_seeEnemyTime + 2.5f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) {
m_moveSpeed = m_fearLevel > m_agressionLevel ? 0.0f : getShiftSpeed ();
m_navTimeset = game.time ();
@ -1785,12 +1786,16 @@ void Bot::refreshEnemyPredict () {
}
const bool denyLastEnemy = pev->velocity.lengthSq2d () > 0.0f
&& distanceToLastEnemySq < cr::sqrf (256.0f)
&& m_shootTime + 2.5f > game.time ();
&& m_shootTime + 1.5f > game.time ();
if (!denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
const auto weaponPenetratePower = conf.findWeaponById (m_currentWeapon).penetratePower;
if (isPenetrableObstacle3 (m_lastEnemyOrigin, weaponPenetratePower)) {
m_aimFlags |= AimFlags::LastEnemy;
}
}
}
if (m_aimFlags & AimFlags::PredictPath) {
updatePredictedIndex ();
@ -1801,7 +1806,7 @@ void Bot::setLastVictim (edict_t *ent) {
m_lastVictim = ent;
m_lastVictimOrigin = ent->v.origin + ent->v.view_ofs;
m_forgetLastVictimTimer.start (rg.get (0.5f, 0.8f));
m_forgetLastVictimTimer.start (rg.get (1.0f, 2.0f));
}
void Bot::setConditions () {
@ -3869,7 +3874,7 @@ void Bot::updateHearing () {
}
// did the bot hear someone ?
if (hearedEnemy != nullptr && util.isPlayer (hearedEnemy)) {
if (util.isPlayer (hearedEnemy)) {
// change to best weapon if heard something
if (m_shootTime < game.time () - 5.0f
&& isOnFloor ()
@ -3889,10 +3894,20 @@ void Bot::updateHearing () {
pushChatterMessage (Chatter::HeardTheEnemy);
}
auto getHeardOriginWithError = [&] () -> Vector {
auto error = kSprayDistance * cr::powf (nearestDistanceSq, 0.5f) / 2048.0f;
auto origin = hearedEnemy->v.origin;
origin.x = origin.x + rg.get (-error, error);
origin.y = origin.y + rg.get (-error, error);
return origin;
};
// didn't bot already have an enemy ? take this one...
if (m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) {
m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = hearedEnemy->v.origin;
m_lastEnemyOrigin = getHeardOriginWithError ();
}
// bot had an enemy, check if it's the heard one
@ -3902,7 +3917,7 @@ void Bot::updateHearing () {
if (m_states & Sense::SeeingEnemy) {
return;
}
m_lastEnemyOrigin = hearedEnemy->v.origin;
m_lastEnemyOrigin = getHeardOriginWithError ();
}
else {
// if bot had an enemy but the heard one is nearer, take it instead
@ -3910,7 +3925,7 @@ void Bot::updateHearing () {
if (distanceSq > hearedEnemy->v.origin.distanceSq (pev->origin) && m_seeEnemyTime + 2.0f < game.time ()) {
m_lastEnemy = hearedEnemy;
m_lastEnemyOrigin = hearedEnemy->v.origin;
m_lastEnemyOrigin = getHeardOriginWithError ();
}
else {
return;

View file

@ -12,6 +12,7 @@ ConVar cv_ignore_enemies ("ignore_enemies", "0", "Enables or disables searching
ConVar cv_check_enemy_rendering ("check_enemy_rendering", "0", "Enables or disables checking enemy rendering flags. Useful for some mods.");
ConVar cv_check_enemy_invincibility ("check_enemy_invincibility", "0", "Enables or disables checking enemy invincibility. Useful for some mods.");
ConVar cv_stab_close_enemies ("stab_close_enemies", "1", "Enables or disables bot ability to stab the enemy with knife if bot is in good condition.");
ConVar cv_use_engine_pvs_check ("use_engine_pvs_check", "0", "Use engine to check potential visibility of a enemy.");
ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::GameRef);
ConVar sv_gravity ("sv_gravity", nullptr, Var::GameRef);
@ -293,7 +294,12 @@ bool Bot::lookupEnemies () {
// the old enemy is no longer visible or
if (game.isNullEntity (newEnemy)) {
auto set = game.getVisibilitySet (this, true); // setup potential visibility set from engine
auto set = nullptr;
// setup potential visibility set from engine
if (cv_use_engine_pvs_check.bool_ ()) {
game.getVisibilitySet (this, true);
}
// ignore shielded enemies, while we have real one
edict_t *shieldEnemy = nullptr;
@ -306,7 +312,7 @@ bool Bot::lookupEnemies () {
}
// check the engine PVS
if (!game.checkVisibility (interesting, set)) {
if (cv_use_engine_pvs_check.bool_ () && !game.checkVisibility (interesting, set)) {
continue;
}
@ -336,7 +342,7 @@ bool Bot::lookupEnemies () {
player = client.ent;
// check the engine PVS
if (!game.checkVisibility (player, set)) {
if (cv_use_engine_pvs_check.bool_ () && !game.checkVisibility (player, set)) {
continue;
}
@ -514,7 +520,7 @@ bool Bot::lookupEnemies () {
}
Vector Bot::getBodyOffsetError (float distance) {
if (game.isNullEntity (m_enemy) || distance < kDoubleSprayDistance) {
if (game.isNullEntity (m_enemy) || distance < kSprayDistanceX2) {
return nullptr;
}
@ -570,7 +576,12 @@ Vector Bot::getEnemyBodyOffset () {
else if (util.isPlayer (m_enemy)) {
// now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
const auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
// with to much recoil or using specific weapons choice to aim to the chest
if (distance > kSprayDistance && (isRecoilHigh () || usesSniperAWP () || usesShotgun ())) {
headshotPct = 0;
}
// now check is our skill match to aim at head, else aim at enemy body
if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) {
@ -639,10 +650,10 @@ Vector Bot::getCustomHeight (float distance) {
auto distanceIndex = DistanceIndex::Short;
// set distance index appropriate to distance
if (distance < 2048.0f && distance > kDoubleSprayDistance) {
if (distance < 2048.0f && distance > kSprayDistanceX2) {
distanceIndex = DistanceIndex::Long;
}
else if (distance > kSprayDistance && distance <= kDoubleSprayDistance) {
else if (distance > kSprayDistance && distance <= kSprayDistanceX2) {
distanceIndex = DistanceIndex::Middle;
}
return { 0.0f, 0.0f, kOffsetRanges[m_weaponType][distanceIndex] };
@ -834,24 +845,23 @@ bool Bot::needToPauseFiring (float distance) {
if (distance < kSprayDistance) {
return false;
}
else if (distance < kDoubleSprayDistance) {
else if (distance < kSprayDistanceX2) {
offset = 2.75f;
}
else if ((m_states & Sense::SuspectEnemy) && distance < kDoubleSprayDistance) {
else if ((m_states & Sense::SuspectEnemy) && distance < kSprayDistanceX2) {
return false;
}
const float xPunch = cr::sqrf (cr::deg2rad (pev->punchangle.x));
const float yPunch = cr::sqrf (cr::deg2rad (pev->punchangle.y));
const float interval = m_frameInterval;
const float tolerance = (100.0f - static_cast <float> (m_difficulty) * 25.0f) / 99.0f;
const float baseTime = distance > kDoubleSprayDistance ? 0.65f : 0.48f;
const float baseTime = distance > kSprayDistance ? 0.65f : 0.48f;
const float maxRecoil = static_cast <float> (conf.getDifficultyTweaks (m_difficulty)->maxRecoil);
// check if we need to compensate recoil
if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) {
if (m_firePause < game.time ()) {
m_firePause = game.time () + rg.get (baseTime, baseTime + maxRecoil * 0.01f * tolerance) - interval;
m_firePause = game.time () + rg.get (baseTime, baseTime + maxRecoil * 0.01f * tolerance) - m_frameInterval;
}
return true;
}
@ -1229,16 +1239,9 @@ void Bot::attackMovement () {
// only take cover when bomb is not planted and enemy can see the bot or the bot is VIP
if (!game.is (GameFlags::CSDM)) {
if (m_retreatTime < game.time () && approach < 30 && !bots.isBombPlanted ()) {
const auto enemyCone = util.getConeDeviation (m_enemy, pev->origin);
const auto seeingEnemy = (m_states & Sense::SeeingEnemy);
const auto enemyWeaponIsSniper = (m_enemy->v.weapons & kSniperWeaponMask);
// make bot seek cover
if ((enemyCone > 0.8f && seeingEnemy) || (enemyWeaponIsSniper && enemyCone > 0.95f)) {
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, false);
if ((m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
m_moveSpeed = -pev->maxspeed;
}
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
}
else if (approach < 50) {
m_moveSpeed = 0.0f;
@ -1292,7 +1295,7 @@ void Bot::attackMovement () {
if (isDucking () || isInNarrowPlace ()) {
m_fightStyle = Fight::Stay;
}
const auto pistolStrafeDistance = game.is (GameFlags::CSDM) ? kDoubleSprayDistance * 3.0f : kDoubleSprayDistance;
const auto pistolStrafeDistance = game.is (GameFlags::CSDM) ? kSprayDistanceX2 * 3.0f : kSprayDistanceX2;
// 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 ())
@ -1317,7 +1320,7 @@ void Bot::attackMovement () {
};
auto strafeUpdateTime = [] () {
return game.time () + rg.get (0.8f, 1.25f);
return game.time () + rg.get (0.3f, 1.0f);
};
// to start strafing, we have to first figure out if the target is on the left side or right side
@ -1392,7 +1395,7 @@ void Bot::attackMovement () {
if (alreadyDucking) {
m_duckTime = game.time () + m_frameInterval * 3.0f;
}
else if ((distance > 768.0f && hasPrimaryWeapon ())
else if ((distance > kSprayDistanceX2 && hasPrimaryWeapon ())
&& (m_enemyParts & (Visibility::Head | Visibility::Body))
&& getCurrentTaskId () != Task::SeekCover
&& getCurrentTaskId () != Task::Hunt) {
@ -1409,7 +1412,7 @@ void Bot::attackMovement () {
}
if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) {
if (distance < kDoubleSprayDistance) {
if (distance < kSprayDistanceX2) {
if (rg.get (0, 1000) < rg.get (5, 10) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
pev->button |= IN_JUMP;
}

View file

@ -496,6 +496,10 @@ int BotControl::cmdNodeOn () {
}
if (graph.hasEditFlag (GraphEdit::On)) {
m_graphSaveVarValues.roundtime = mp_roundtime.float_ ();
m_graphSaveVarValues.freezetime = mp_freezetime.float_ ();
m_graphSaveVarValues.timelimit = mp_timelimit.float_ ();
mp_roundtime.set (9);
mp_freezetime.set (0);
mp_timelimit.set (0);
@ -511,6 +515,11 @@ int BotControl::cmdNodeOff () {
graph.clearEditFlag (GraphEdit::On | GraphEdit::Auto | GraphEdit::Noclip);
enableDrawModels (false);
// revert cvars back to their values
mp_roundtime.set (m_graphSaveVarValues.roundtime);
mp_freezetime.set (m_graphSaveVarValues.freezetime);
mp_timelimit.set (m_graphSaveVarValues.timelimit);
msg ("Graph editor has been disabled.");
}
else if (strValue (option) == "models") {

View file

@ -379,7 +379,7 @@ bool Game::checkVisibility (edict_t *ent, uint8_t *set) {
return false;
}
for (int i = 0; i < 48; ++i) {
for (int i = 0; i < MAX_ENT_LEAFS; ++i) {
const auto leaf = ent->leafnums[i];
if (leaf == -1) {

View file

@ -14,7 +14,7 @@ ConVar cv_kick_after_player_connect ("kick_after_player_connect", "1", "Kick the
ConVar cv_quota ("quota", "9", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar cv_quota_mode ("quota_mode", "normal", "Specifies the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is yb_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is yb_quota_match.", false);
ConVar cv_quota_match ("quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar cv_think_fps ("think_fps", "26.0", "Specifies how many times per second bot code will run.", true, 24.0f, 90.0f);
ConVar cv_think_fps ("think_fps", "30.0", "Specifies how many times per second bot code will run.", true, 24.0f, 90.0f);
ConVar cv_think_fps_disable ("think_fps_disable", "0", "Allows to completely disable think fps on Xash3D.", true, 0.0f, 1.0f, Var::Xash3D);
ConVar cv_autokill_delay ("autokill_delay", "0.0", "Specifies amount of time in seconds when bots will be killed if no humans left alive.", true, 0.0f, 90.0f);

View file

@ -975,7 +975,7 @@ bool Bot::updateNavigation () {
const auto prevNodeIndex = m_previousNodes[0];
// do a precise movement when very near
if (graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
if (!isDucking () && graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
m_moveSpeed = pev->maxspeed * 0.4f;
// do not duck while not on ladder
@ -1114,14 +1114,14 @@ bool Bot::updateNavigation () {
}
float desiredDistanceSq = cr::sqrf (4.0f);
const float nodeDistanceSq = pev->origin.distanceSq (m_pathOrigin);
const float nodeDistanceSq = pev->origin.distanceSq2d (m_pathOrigin);
// initialize the radius for a special node type, where the node is considered to be reached
if (m_pathFlags & NodeFlag::Lift) {
desiredDistanceSq = cr::sqrf (50.0f);
}
else if (isDucking () || (m_pathFlags & NodeFlag::Goal)) {
desiredDistanceSq = cr::sqrf (25.0f);
desiredDistanceSq = cr::sqrf (12.0f);
// on cs_ maps goals are usually hostages, so increase reachability distance for them, they (hostages) picked anyway
if (game.mapIs (MapFlags::HostageRescue) && (m_pathFlags & NodeFlag::Goal)) {

View file

@ -463,7 +463,6 @@ void Bot::updateLookAngles () {
m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f);
pev->v_angle = m_idealAngles;
pev->v_angle.clampAngles ();
updateBodyAngles ();
}