vistable: fix long-standing bug with saving vis counts

vistable: bumped version to 4, so all vistables will be rebuilded
bot: reworked bot think mechanism timers
fix: gimbal lock within bot aiming code on ladders
fix: some fixes to aiming code that prevent bots 360 degree rotations
fix: some mistakes in next and next-next node aiming when in idle state
fix: improved seek covering from attack task
nav: improved bot's crouch on marred-crouch nodes
nav: overall improvements to ladder handling code
Co-Authored-By: Max <161382234+dyspose@users.noreply.github.com>
This commit is contained in:
jeefo 2024-04-17 21:20:45 +03:00
commit 38551eae21
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
11 changed files with 151 additions and 98 deletions

View file

@ -430,7 +430,7 @@ constexpr auto kGrenadeCheckTime = 0.6f;
constexpr auto kSprayDistance = 360.0f;
constexpr auto kSprayDistanceX2 = kSprayDistance * 2;
constexpr auto kMaxChatterRepeatInterval = 99.0f;
constexpr auto kViewFrameUpdate = 1.0f / 30.0f;
constexpr auto kViewFrameUpdate = 1.0f / 25.0f;
constexpr auto kGrenadeDamageRadius = 385.0f;
constexpr auto kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);

View file

@ -29,7 +29,7 @@ CR_DECLARE_SCOPED_ENUM (StorageOption,
CR_DECLARE_SCOPED_ENUM (StorageVersion,
Graph = 2,
Practice = 2,
Vistable = 3,
Vistable = 4,
Matrix = 2,
Podbot = 7
)

View file

@ -109,6 +109,12 @@ struct Client {
ClientNoise noise;
};
// think delay mapping
struct FrameDelay {
float interval {};
float time {};
};
// include bot graph stuff
#include <graph.h>
#include <vision.h>
@ -235,7 +241,7 @@ private:
float m_knifeAttackTime {}; // time to rush with knife (at the beginning of the round)
float m_duckDefuseCheckTime {}; // time to check for ducking for defuse
float m_frameInterval {}; // bot's frame interval
float m_lastCommandTime {}; // time bot last thinked
float m_previousThinkTime {}; // time bot last thinked
float m_reloadCheckTime {}; // time to check reloading
float m_zoomCheckTime {}; // time to check zoom again
float m_shieldCheckTime {}; // time to check shield drawing again
@ -345,7 +351,9 @@ private:
Path *m_path {}; // pointer to the current path node
String m_chatBuffer {}; // space for strings (say text...)
Frustum::Planes m_viewFrustum {};
CountdownTimer m_forgetLastVictimTimer {}; // time to forget last victim position ?
CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder
private:
int pickBestWeapon (Array <int> &vec, int moneySave);
@ -589,8 +597,6 @@ public:
float m_agressionLevel {}; // dynamic aggression level (in game)
float m_fearLevel {}; // dynamic fear level (in game)
float m_nextEmotionUpdate {}; // next time to sanitize emotions
float m_updateTime {}; // skip some frames in bot thinking
float m_updateInterval {}; // interval between frames
float m_goalValue {}; // ranking value for this node
float m_viewDistance {}; // current view distance
float m_maxViewDistance {}; // maximum view distance
@ -675,9 +681,13 @@ public:
BurstMode m_weaponBurstMode {}; // bot using burst mode? (famas/glock18, but also silencer mode)
Personality m_personality {}; // bots type
Array <BotTask> m_tasks {};
Deque <int32_t> m_msgQueue {};
Array <int32_t> m_goalHist {};
FrameDelay m_thinkDelay {};
FrameDelay m_commandDelay {};
public:
Bot (edict_t *bot, int difficulty, int personality, int team, int skin);

View file

@ -1789,13 +1789,9 @@ void Bot::refreshEnemyPredict () {
&& m_shootTime + 1.5f > game.time ();
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 ();
@ -1804,7 +1800,7 @@ void Bot::refreshEnemyPredict () {
void Bot::setLastVictim (edict_t *ent) {
m_lastVictim = ent;
m_lastVictimOrigin = ent->v.origin + ent->v.view_ofs;
m_lastVictimOrigin = ent->v.origin;
m_forgetLastVictimTimer.start (rg.get (1.0f, 2.0f));
}
@ -2000,7 +1996,7 @@ void Bot::filterTasks () {
float timeHeard = m_heardSoundTime - game.time ();
float ratio = 0.0f;
m_retreatTime = game.time () + rg.get (3.0f, 15.0f);
m_retreatTime = game.time () + rg.get (1.0f, 4.0f);
if (timeSeen > timeHeard) {
timeSeen += 10.0f;
@ -2836,11 +2832,18 @@ void Bot::checkParachute () {
void Bot::frame () {
pev->flags |= FL_CLIENT | FL_FAKECLIENT; // restore fake client bit, just in case
if (m_updateTime <= game.time ()) {
if (m_thinkDelay.time <= game.time ()) {
update ();
// delay next execution for thinking
m_thinkDelay.time = game.time () + m_thinkDelay.interval;
// run bot command on twice speed
if (m_commandDelay.time <= game.time ()) {
runMovement ();
m_commandDelay.time = game.time () + m_commandDelay.interval;
}
else if (m_isAlive) {
updateLookAngles ();
}
if (m_slowFrameTimestamp > game.time ()) {
@ -2955,10 +2958,6 @@ void Bot::update () {
else if (!botMovement) {
resetMovement ();
}
runMovement ();
// delay next execution
m_updateTime = game.time () + m_updateInterval;
}
void Bot::logicDuringFreezetime () {
@ -2966,6 +2965,12 @@ void Bot::logicDuringFreezetime () {
return;
}
// simply skip randomly
if (rg.chance (10)) {
return;
}
pev->button &= ~IN_DUCK;
if (rg.chance (15) && m_jumpTime + rg.get (1.0, 2.0f) < game.time ()) {
pev->button |= IN_JUMP;
m_jumpTime = game.time ();
@ -3215,9 +3220,7 @@ void Bot::logic () {
m_moveSpeed = -pev->maxspeed;
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
}
ensureEntitiesClear ();
translateInput ();
// check if need to use parachute
checkParachute ();
@ -3742,13 +3745,13 @@ bool Bot::canRunHeavyWeight () {
uint8_t Bot::computeMsec () {
// estimate msec to use for this command based on time passed from the previous command
return static_cast <uint8_t> (cr::min (static_cast <int32_t> (cr::roundf ((game.time () - m_lastCommandTime) * 1000.0f)), 255));
return static_cast <uint8_t> (cr::min (static_cast <int32_t> (cr::roundf ((game.time () - m_previousThinkTime) * 1000.0f)), 255));
}
const Vector &Bot::getRpmAngles () {
// get angles to pass to run player move function
if ((m_pathFlags & NodeFlag::Ladder) || getCurrentTaskId () == Task::Attack) {
if (!m_approachingLadderTimer.elapsed () || getCurrentTaskId () == Task::Attack) {
return pev->v_angle;
}
return m_moveAngles;
@ -3769,10 +3772,13 @@ void Bot::runMovement () {
// elapses, that bot will behave like a ghost : no movement, but bullets and players can
// pass through it. Then, when the next frame will begin, the stucking problem will arise !
m_frameInterval = game.time () - m_lastCommandTime;
m_frameInterval = game.time () - m_previousThinkTime;
const auto msecVal = computeMsec ();
m_lastCommandTime = game.time ();
m_previousThinkTime = game.time ();
// translate bot buttons
translateInput ();
engfuncs.pfnRunPlayerMove (pev->pContainingEntity,
getRpmAngles (), m_moveSpeed, m_strafeSpeed,
@ -3946,7 +3952,6 @@ void Bot::updateHearing () {
// check if heard enemy can be shoot through some obstacle
else {
if (cv_shoots_thru_walls.bool_ ()
&& m_difficulty >= Difficulty::Normal
&& m_lastEnemy == hearedEnemy
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct)
&& m_seeEnemyTime + 3.0f > game.time ()

View file

@ -7,7 +7,7 @@
#include <yapb.h>
ConVar cv_shoots_thru_walls ("shoots_thru_walls", "1", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f);
ConVar cv_shoots_thru_walls ("shoots_thru_walls", "2", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f);
ConVar cv_ignore_enemies ("ignore_enemies", "0", "Enables or disables searching world for enemies.");
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.");
@ -236,11 +236,6 @@ bool Bot::seesEnemy (edict_t *player) {
&& frustum.check (m_viewFrustum, player)
&& !isBehindSmokeClouds (player->v.origin)
&& checkBodyParts (player)) {
m_seeEnemyTime = game.time ();
m_lastEnemy = player;
m_lastEnemyOrigin = m_enemyOrigin;
return true;
}
return false;
@ -274,8 +269,14 @@ bool Bot::lookupEnemies () {
}
else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f > game.time () && util.isAlive (m_lastEnemy)) {
m_states |= Sense::SuspectEnemy;
// check if last enemy can be penetrated
const auto penetratePower = conf.findWeaponById (m_currentWeapon).penetratePower * 4;
if (isPenetrableObstacle3 (m_lastEnemyOrigin, penetratePower)) {
m_aimFlags |= AimFlags::LastEnemy;
}
}
m_enemyParts = Visibility::None;
m_enemyOrigin = nullptr;
@ -475,7 +476,6 @@ bool Bot::lookupEnemies () {
// if no enemy visible check if last one shoot able through wall
if (cv_shoots_thru_walls.bool_ ()
&& m_difficulty >= Difficulty::Normal
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct)
&& isPenetrableObstacle (newEnemy->v.origin)) {
@ -579,7 +579,7 @@ Vector Bot::getEnemyBodyOffset () {
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 ())) {
if (distance > kSprayDistance && (isRecoilHigh () || usesShotgun ())) {
headshotPct = 0;
}
@ -587,6 +587,10 @@ Vector Bot::getEnemyBodyOffset () {
if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) {
spot = m_enemyOrigin + getCustomHeight (distance);
if (usesSniper ()) {
spot.z -= pev->view_ofs.z * 0.5f;
}
// set's the enemy shooting spot to head, if headshot pct allows, and use head for that
// enemy until new enemy is acquired, to prevent too shaky aiming
m_enemyBodyPartSet = m_enemy;
@ -698,8 +702,6 @@ bool Bot::isPenetrableObstacle (const Vector &dest) {
// this function returns true if enemy can be shoot through some obstacle, false otherwise.
// credits goes to Immortal_BLG
const auto method = cv_shoots_thru_walls.int_ ();
if (m_isUsingGrenade || m_difficulty < Difficulty::Normal) {
return false;
}
@ -708,6 +710,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) {
if (penetratePower == 0) {
return false;
}
const auto method = cv_shoots_thru_walls.int_ ();
// switch methods
switch (method) {
@ -989,8 +992,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
}
}
}
if (pev->button & IN_ATTACK) {
m_shootTime = game.time ();
}
}
else {
// don't attack with knife over long distance
if (id == Weapon::Knife) {
@ -1239,8 +1245,7 @@ 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_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
m_moveSpeed = -pev->maxspeed;
if (m_retreatTime < game.time () && (m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
}
else if (approach < 50) {

View file

@ -10,6 +10,7 @@
ConVar cv_display_menu_text ("display_menu_text", "1", "Enables or disables display menu text, when players asks for menu. Useful only for Android.", true, 0.0f, 1.0f, Var::Xash3D);
ConVar cv_password ("password", "", "The value (password) for the setinfo key, if user sets correct password, he's gains access to bot commands and menus.", false, 0.0f, 0.0f, Var::Password);
ConVar cv_password_key ("password_key", "_ybpw", "The name of setinfo key used to store password to bot commands and menus.", false);
ConVar cv_bots_kill_on_endround ("bots_kill_on_endround", "0", "Allows to use classic bot kill on issuing end-round command in menus, instead of gamedll endround.", false);
int BotControl::cmdAddBot () {
enum args { alias = 1, difficulty, personality, team, model, name, max };
@ -1025,7 +1026,7 @@ int BotControl::menuMain (int item) {
break;
case 4:
if (game.is (GameFlags::ReGameDLL)) {
if (game.is (GameFlags::ReGameDLL) && !cv_bots_kill_on_endround.bool_ ()) {
game.serverCommand ("endround");
}
else {

View file

@ -1151,7 +1151,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
}
m_basePing = rg.get (cv_ping_base_min.int_ (), cv_ping_base_max.int_ ());
m_lastCommandTime = game.time () - 0.1f;
m_previousThinkTime = game.time () - 0.1f;
m_frameInterval = game.time ();
m_heavyTimestamp = game.time ();
m_slowFrameTimestamp = 0.0f;
@ -1620,7 +1620,7 @@ void Bot::newRound () {
if (rg.chance (50)) {
pushChatterMessage (Chatter::NewRound);
}
auto thinkFps = cr::clamp (cv_think_fps.float_ (), 24.0f, 90.0f);
auto thinkFps = cr::clamp (cv_think_fps.float_ (), 30.0f, 90.0f);
auto updateInterval = 1.0f / thinkFps;
if (game.is (GameFlags::Xash3D)) {
@ -1634,7 +1634,8 @@ void Bot::newRound () {
else if (game.is (GameFlags::Legacy)) {
updateInterval = 0.0f; // legacy games behaves strange, when this enabled
}
m_updateInterval = updateInterval;
m_thinkDelay.interval = updateInterval;
m_commandDelay.interval = 1.0f / 60.0f;
}
void Bot::resetPathSearchType () {

View file

@ -820,9 +820,26 @@ void Bot::moveToGoal () {
// press duck button if we need to
if (m_pathFlags & NodeFlag::Crouch) {
constexpr auto kEndRouteThreshold = 3;
bool pressDuck = true;
if (!(m_pathFlags & (NodeFlag::Camp | NodeFlag::Goal)) || m_pathWalk.length () < kEndRouteThreshold) {
if (m_pathFlags & (NodeFlag::Camp | NodeFlag::Goal)) {
TraceResult tr {};
auto src = m_path->origin;
auto dst = m_path->origin;
src.z += 12.0f;
dst.z += 18.0f + 28.0f;
game.testLine (src, dst, TraceIgnore::Everything, ent (), &tr);
if (tr.flFraction >= 0.95f) {
pressDuck = false;
}
}
// press duck if not canceled by visibility count check only and it's end of the route
if (pressDuck) {
pev->button |= IN_DUCK;
}
}
@ -899,7 +916,7 @@ bool Bot::updateNavigation () {
m_navTimeset = game.time ();
}
m_destOrigin = m_pathOrigin + pev->view_ofs;
m_destOrigin = m_pathOrigin;
// this node has additional travel flags - care about them
if (m_currentTravelFlags & PathFlag::Jump) {
@ -962,7 +979,7 @@ bool Bot::updateNavigation () {
const float ladderDistance = pev->origin.distance (m_pathOrigin);
if (m_pathOrigin.z >= pev->origin.z + 16.0f) {
constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f);
constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 36.0f);
m_pathOrigin = m_path->origin + kLadderOffset;
}
@ -979,13 +996,16 @@ bool Bot::updateNavigation () {
const auto prevNodeIndex = m_previousNodes[0];
// do a precise movement when very near
if (!isDucking () && graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
if (graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
if (!isDucking ()) {
m_moveSpeed = pev->maxspeed * 0.4f;
}
// do not duck while not on ladder
if (!isOnLadder ()) {
pev->button &= ~IN_DUCK;
}
m_approachingLadderTimer.start (m_frameInterval * 4.0f);
}
// special detection if someone is using the ladder (to prevent to have bots-towers on ladders)
@ -1133,7 +1153,7 @@ bool Bot::updateNavigation () {
}
}
else if (m_pathFlags & NodeFlag::Ladder) {
desiredDistanceSq = cr::sqrf (8.0f);
desiredDistanceSq = cr::sqrf (16.0f);
}
else if (m_currentTravelFlags & PathFlag::Jump) {
desiredDistanceSq = 0.0f;
@ -1142,7 +1162,7 @@ bool Bot::updateNavigation () {
desiredDistanceSq = 0.0f;
}
else if (isOccupiedNode (m_path->number)) {
desiredDistanceSq = cr::sqrf (96.0f);
desiredDistanceSq = cr::sqrf (102.0f);
}
else {
desiredDistanceSq = cr::max (cr::sqrf (m_path->radius), desiredDistanceSq);
@ -1157,9 +1177,9 @@ bool Bot::updateNavigation () {
}
// needs precise placement - check if we get past the point
if (desiredDistanceSq < cr::sqrf (22.0f)
if (desiredDistanceSq < cr::sqrf (16.0f)
&& nodeDistanceSq < cr::sqrf (30.0f)
&& m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= nodeDistanceSq) {
&& m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) > nodeDistanceSq) {
desiredDistanceSq = nodeDistanceSq + 1.0f;
}
@ -3127,7 +3147,7 @@ bool Bot::isReachableNode (int index) {
const Vector &dst = graph[index].origin;
// is the destination close enough?
if (dst.distanceSq (src) >= cr::sqrf (600.0f)) {
if (dst.distanceSq (src) >= cr::sqrf (400.0f)) {
return false;
}

View file

@ -184,6 +184,14 @@ template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *
graph.setExtenHeader (&extenHeader);
}
}
// for visibility tables load counts of stand/count numbers
if (type.option & StorageOption::Vistable) {
for (auto &path : graph) {
file.read (&path.vis, sizeof (PathVis));
}
}
ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f);
file.close ();
@ -250,6 +258,13 @@ template <typename U> bool BotStorage::save (const SmallArray <U> &data, ExtenHe
file.write (&hdr, sizeof (StorageHeader));
file.write (compressed.data (), sizeof (uint8_t), compressedLength);
// for visibility tables save counts of stand/count numbers
if (type.option & StorageOption::Vistable) {
for (auto &path : graph) {
file.write (&path.vis, sizeof (PathVis));
}
}
// add extension
if ((type.option & StorageOption::Exten) && exten != nullptr) {
file.write (exten, sizeof (ExtenHeader));

View file

@ -33,7 +33,7 @@ void Bot::normal_ () {
if (m_currentNodeIndex == debugGoal) {
const auto &debugOrigin = graph[debugGoal].origin;
if (debugOrigin.distanceSq (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) {
if (debugOrigin.distanceSq2d (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) {
m_moveToGoal = false;
m_checkTerrain = false;
@ -468,10 +468,10 @@ void Bot::seekCover_ () {
destIndex = getTask ()->data;
}
else {
destIndex = findCoverNode (usesSniper () ? 256.0f : 512.0f);
destIndex = findCoverNode (900.0f);
if (destIndex == kInvalidNodeIndex) {
m_retreatTime = game.time () + rg.get (5.0f, 10.0f);
m_retreatTime = game.time () + rg.get (1.0f, 2.0f);
m_prevGoalIndex = kInvalidNodeIndex;
completeTask ();

View file

@ -175,33 +175,36 @@ void Bot::setAimDirection () {
m_lookAt = m_lookAtSafe;
}
else if (flags & AimFlags::Nav) {
m_lookAt = m_destOrigin;
const auto &destOrigin = m_destOrigin + pev->view_ofs;
m_lookAt = destOrigin;
if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time ()
&& !m_isStuck && m_moveSpeed > getShiftSpeed ()
&& !(pev->button & IN_DUCK)
&& !m_isStuck && !(pev->button & IN_DUCK)
&& m_currentNodeIndex != kInvalidNodeIndex
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
&& m_pathWalk.hasNext ()
&& pev->origin.distanceSq (m_destOrigin) < cr::sqrf (512.0f)) {
&& m_pathWalk.hasNext () && !isOnLadder ()
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) {
const auto nextPathIndex = m_pathWalk.next ();
const auto doubleNextPath = m_pathWalk.doubleNext ();
if (vistab.visible (m_currentNodeIndex, doubleNextPath)) {
m_lookAt = graph[doubleNextPath].origin + pev->view_ofs;
const auto &gn = graph[doubleNextPath];
m_lookAt = gn.origin + pev->view_ofs;
}
else if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs;
const auto &gn = graph[nextPathIndex];
m_lookAt = gn.origin + pev->view_ofs;
}
else {
m_lookAt = m_destOrigin;
m_lookAt = pev->origin + pev->view_ofs + pev->v_angle.forward () * 300.0f;
}
}
else {
m_lookAt = m_destOrigin;
m_lookAt = destOrigin;
}
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || !cr::fzero (pev->velocity.z);
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder ();
if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !horizontalMovement) {
const auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
@ -211,7 +214,7 @@ void Bot::setAimDirection () {
&& !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (512.0f)) {
m_lookAt = m_destOrigin;
m_lookAt = destOrigin;
}
else {
m_lookAt = graph[dangerIndex].origin + pev->view_ofs;
@ -227,21 +230,18 @@ void Bot::setAimDirection () {
const auto &nextPath = graph[m_pathWalk.next ()];
if ((nextPath.flags & NodeFlag::Ladder) && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (128.0f) && nextPath.origin.z > m_pathOrigin.z + 26.0f) {
m_lookAt = nextPath.origin;
m_lookAt = nextPath.origin + pev->view_ofs;
}
else {
m_lookAt = m_destOrigin;
}
// don't look at bottom of node, if reached it
if (m_lookAt == destOrigin && !horizontalMovement) {
m_lookAt.z = getEyesPos ().z;
}
// try to look at last victim for a little, maybe there's some one else
if (game.isNullEntity (m_enemy) && m_difficulty >= Difficulty::Normal && !m_forgetLastVictimTimer.elapsed () && !m_lastVictimOrigin.empty ()) {
m_lookAt = m_lastVictimOrigin;
}
// don't look at bottom of node, if reached it
if (m_lookAt == m_destOrigin && !horizontalMovement) {
m_lookAt.z = getEyesPos ().z;
m_lookAt = m_lastVictimOrigin + pev->view_ofs;
}
}
@ -402,21 +402,15 @@ void Bot::updateLookAngles () {
updateBodyAngles ();
return;
}
float aimSkill = cr::clamp (static_cast <float> (m_difficulty), 3.0f, 4.0f) * 25.0f;
const bool importantAimFlags = (m_aimFlags & (AimFlags::Enemy | AimFlags::Grenade));
// do not slowdown while on ladder
if (isOnLadderPath () || isOnLadder ()) {
aimSkill = 100.0f;
}
const bool importantAimFlags = (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade));
float accelerate = aimSkill * 30.0f;
float stiffness = aimSkill * 2.0f;
float damping = aimSkill * 0.25f;
float accelerate = 3000.0f;
float stiffness = 200.0f;
float damping = 25.0f;
if ((importantAimFlags || m_wantsToFire) && m_difficulty > Difficulty::Normal) {
if (m_difficulty == Difficulty::Expert) {
accelerate += 600.0f;
accelerate += 300.0f;
}
stiffness += 100.0f;
damping -= 5.0f;
@ -427,8 +421,8 @@ void Bot::updateLookAngles () {
float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
// prevent reverse facing angles when navigating normally
if (m_difficulty < Difficulty::Easy && m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) {
const float forward = (m_pathOrigin - pev->origin).yaw ();
if (m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) {
const float forward = (m_lookAt - pev->origin).yaw ();
if (!cr::fzero (forward)) {
const float current = cr::wrapAngle (pev->v_angle.y - forward);
@ -460,9 +454,11 @@ void Bot::updateLookAngles () {
const float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate);
m_lookPitchVel += delta * accel;
m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f);
m_idealAngles.x += delta * m_lookPitchVel;
pev->v_angle = m_idealAngles.clampAngles ();
m_idealAngles.x = cr::clamp (m_idealAngles.x, -89.0f, 89.0f);
pev->v_angle = m_idealAngles;
pev->v_angle.z = 0.0f;
updateBodyAngles ();