bot: return of the cheat cvar yb_whose_your_daddy (resolved #513)

combat: resolve strafe movement issues
combat: resolve bots always standing still with pistols and shotguns
vision: take a look at recent victim for some time before changing view angles
control: allow bots to be killed silently (ref #514) via commands
control: bots that are killed with auto kill timer are now killed silently
This commit is contained in:
jeefo 2024-01-29 08:08:07 +03:00
commit d82124e595
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
14 changed files with 150 additions and 62 deletions

View file

@ -2001,6 +2001,12 @@ Specifies the language for bot messages and menus.
[TRANSLATED]
Gibt die Sprache für Bot-Meldungen und Menüs an.
[ORIGINAL]
Enables or disables extra hard difficulty for bots.
[TRANSLATED]
Aktiviert oder deaktiviert den extra schweren Schwierigkeitsgrad für Bots.
[ORIGINAL]
Selects the heuristic function mode. For debug purposes only.

View file

@ -2433,6 +2433,12 @@ Specifies minimum amount of seconds bot keep connected, if rotation active.
[TRANSLATED]
Задаёт минимальное количество секунд, в течение которых бот остаётся подключённым, если чередование активно.
[ORIGINAL]
Enables or disables extra hard difficulty for bots.
[TRANSLATED]
Включает или выключает очень тяжёлую сложность для ботов.
[ORIGINAL]
Specifies maximum amount of seconds bot keep connected, if rotation active.

@ -1 +1 @@
Subproject commit fa4e412972f6dcc136b3f687bd76ef46c18d923f
Subproject commit aa8e1e0eaed2e4b75e6b2a0fff5a68756cddc972

View file

@ -95,7 +95,7 @@ public:
void kickEveryone (bool instant = false, bool zeroQuota = true);
void kickBot (int index);
void kickFromTeam (Team team, bool removeAll = false);
void killAllBots (int team = -1);
void killAllBots (int team = -1, bool silent = false);
void maintainQuota ();
void maintainAutoKill ();
void maintainLeaders ();

View file

@ -346,6 +346,7 @@ 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 ?
private:
int pickBestWeapon (Array <int> &vec, int moneySave);
@ -441,7 +442,7 @@ private:
void checkGrenadesThrow ();
void checkBurstMode (float distance);
void checkSilencer ();
void updateAimDir ();
void setAimDirection ();
void updateLookAngles ();
void updateBodyAngles ();
void updateLookAnglesNewbie (const Vector &direction, float delta);
@ -492,6 +493,7 @@ private:
void moveToGoal ();
void resetMovement ();
void refreshEnemyPredict ();
void setLastVictim (edict_t *victim);
void normal_ ();
void spraypaint_ ();
@ -646,6 +648,7 @@ public:
bool m_isEnemyReachable {}; // direct line to enemy
bool m_kickedByRotation {}; // is bot kicked due to rotation ?
bool m_kickMeFromServer {}; // kick the bot off the server?
bool m_fireHurtsFriend {}; // firing at enemy will hurt our friend?
edict_t *m_doubleJumpEntity {}; // pointer to entity that request double jump
edict_t *m_radioEntity {}; // pointer to entity issuing a radio command
@ -659,6 +662,7 @@ public:
Vector m_position {}; // position to move to in move to position task
Vector m_doubleJumpOrigin {}; // origin of double jump
Vector m_lastEnemyOrigin {}; // vector to last enemy origin
Vector m_lastVictimOrigin {}; // last victim origin to watch it
ChatCollection m_sayTextBuffer {}; // holds the index & the actual message of the last unprocessed text message of a player
BurstMode m_weaponBurstMode {}; // bot using burst mode? (famas/glock18, but also silencer mode)
@ -862,6 +866,7 @@ extern ConVar cv_graph_url_upload;
extern ConVar cv_graph_auto_save_count;
extern ConVar cv_graph_analyze_max_jump_height;
extern ConVar cv_spraypaints;
extern ConVar cv_whose_your_daddy;
extern ConVar mp_freezetime;
extern ConVar mp_roundtime;

View file

@ -263,6 +263,12 @@ edict_t *Bot::lookupBreakable () {
}
void Bot::setIdealReactionTimers (bool actual) {
if (cv_whose_your_daddy.bool_ ()) {
m_idealReactionTime = 0.05f;
m_actualReactionTime = 0.095f;
return; // zero out reaction times for extreme mode
}
const auto tweak = conf.getDifficultyTweaks (m_difficulty);
if (actual) {
@ -1698,6 +1704,13 @@ void Bot::refreshEnemyPredict () {
}
}
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));
}
void Bot::setConditions () {
// this function carried out each frame. does all of the sensing, calculates emotions and finally sets the desired
// action after applying all of the Filters
@ -3014,7 +3027,7 @@ void Bot::logic () {
m_isUsingGrenade = false;
executeTasks (); // execute current task
updateAimDir (); // choose aim direction
setAimDirection (); // choose aim direction
updateLookAngles (); // and turn to chosen aim direction
// the bots wants to fire at something?
@ -3193,7 +3206,7 @@ void Bot::showDebugOverlay () {
}
StringRef weapon = util.weaponIdToAlias (m_currentWeapon);
StringRef debugData = strings.format (
"\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\n"
"\n\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\n"
"Item: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\n"
"SP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\n"
"Enemy=%s Pickup=%s Type=%s Terrain=%s Stuck=%s\n",

View file

@ -218,8 +218,13 @@ bool Bot::seesEnemy (edict_t *player) {
if (game.isNullEntity (player)) {
return false;
}
bool ignoreFieldOfView = false;
if (isInViewCone (player->v.origin) && frustum.check (m_viewFrustum, player) && checkBodyParts (player)) {
if (cv_whose_your_daddy.bool_ () && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) {
ignoreFieldOfView = true;
}
if ((ignoreFieldOfView || isInViewCone (player->v.origin)) && frustum.check (m_viewFrustum, player) && checkBodyParts (player)) {
m_seeEnemyTime = game.time ();
m_lastEnemy = player;
m_lastEnemyOrigin = m_enemyOrigin;
@ -314,6 +319,11 @@ bool Bot::lookupEnemies () {
continue;
}
// extra skill player can see thru smoke... if beeing attacked
if (cv_whose_your_daddy.bool_ () && (player->v.button & (IN_ATTACK | IN_ATTACK2)) && m_viewDistance < m_maxViewDistance) {
nearestDistanceSq = cr::sqrf (m_maxViewDistance);
}
// see if bot can see the player...
if (seesEnemy (player)) {
if (isEnemyBehindShield (player)) {
@ -362,7 +372,13 @@ bool Bot::lookupEnemies () {
pushRadioMessage (Radio::EnemySpotted);
}
m_targetEntity = nullptr; // stop following when we see an enemy...
m_enemySurpriseTime = game.time () + m_actualReactionTime;
if (cv_whose_your_daddy.bool_ ()) {
m_enemySurpriseTime = m_actualReactionTime * 0.5f;
}
else {
m_enemySurpriseTime = m_actualReactionTime;
}
// zero out reaction time
m_actualReactionTime = 0.0f;
@ -976,8 +992,12 @@ void Bot::fireWeapons () {
// or if friend in line of fire, stop this too but do not update shoot time
if (isFriendInLineOfFire (distance)) {
m_fireHurtsFriend = true;
return;
}
else {
m_fireHurtsFriend = false;
}
int selectId = Weapon::Knife, selectIndex = 0, choosenWeapon = 0;
const auto tab = conf.getRawWeapons ();
@ -1148,7 +1168,7 @@ void Bot::attackMovement () {
}
auto approach = 0;
const auto distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum?
const auto distance = m_lookAt.distance (getEyesPos ()); // how far away is the enemy scum?
if (usesKnife ()) {
approach = 100;
@ -1168,7 +1188,8 @@ 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) && (m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
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;
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
}
@ -1178,6 +1199,7 @@ void Bot::attackMovement () {
else {
m_moveSpeed = pev->maxspeed;
}
}
if (m_lastFightStyleCheck + 3.0f < game.time ()) {
if (usesSniper ()) {
@ -1215,9 +1237,6 @@ void Bot::attackMovement () {
else if (usesKnife ()) {
m_fightStyle = Fight::Strafe;
}
else if (usesKnife () && isInViewCone (m_enemy->v.origin) && game.is (GameFlags::CSDM) && !isInNarrowPlace ()) {
m_fightStyle = Fight::Strafe;
}
else {
m_fightStyle = Fight::Stay;
}
@ -1226,6 +1245,14 @@ void Bot::attackMovement () {
if (isDucking () || isInNarrowPlace ()) {
m_fightStyle = Fight::Stay;
}
const auto pistolStrafeDistance = game.is (GameFlags::CSDM) ? kDoubleSprayDistance * 3.0f : kDoubleSprayDistance;
// fire hurts friend value here is from previous frame, but acceptable, and saves us alot of cpu cycles
if (m_fireHurtsFriend || ((usesPistol () || usesShotgun ())
&& distance < pistolStrafeDistance
&& isInViewCone (m_enemyOrigin))) {
m_fightStyle = Fight::Strafe;
}
m_lastFightStyleCheck = game.time ();
}
@ -1252,10 +1279,10 @@ void Bot::attackMovement () {
const auto &rightSide = m_enemy->v.v_angle.right ().normalize2d_apx ();
if ((dirToPoint | rightSide) < 0.0f) {
m_combatStrafeDir = Dodge::Left;
m_combatStrafeDir = Dodge::Right;
}
else {
m_combatStrafeDir = Dodge::Right;
m_combatStrafeDir = Dodge::Left;
}
if (rg.chance (30)) {
@ -1267,14 +1294,14 @@ void Bot::attackMovement () {
const bool wallOnRight = checkWallOnRight ();
const bool wallOnLeft = checkWallOnLeft ();
if (m_combatStrafeDir == Dodge::Right) {
if (m_combatStrafeDir == Dodge::Left) {
if (!wallOnLeft) {
m_strafeSpeed = -pev->maxspeed;
}
else if (!wallOnRight) {
swapStrafeCombatDir ();
m_strafeSetTime = strafeUpdateTime ();
m_strafeSetTime = strafeUpdateTime ();
m_strafeSpeed = pev->maxspeed;
}
else {
@ -1288,8 +1315,8 @@ void Bot::attackMovement () {
}
else if (!wallOnLeft) {
swapStrafeCombatDir ();
m_strafeSetTime = strafeUpdateTime ();
m_strafeSetTime = strafeUpdateTime ();
m_strafeSpeed = -pev->maxspeed;
}
else {
@ -1298,14 +1325,12 @@ void Bot::attackMovement () {
}
}
// we're setting strafe speed regardless of move angles, so not resetting forward move here cause bots to behave strange
m_moveSpeed = 0.0f;
if (m_difficulty >= Difficulty::Normal && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.get (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 150.0f) && !usesSniper ()) {
pev->button |= IN_JUMP;
}
// do not move forward/backward is too far
if (distance > 1024.0f) {
m_moveSpeed = 0.0f;
}
}
else if (m_fightStyle == Fight::Stay) {
const bool alreadyDucking = m_duckTime > game.time () || isDucking ();
@ -1649,16 +1674,18 @@ void Bot::updateTeamCommands () {
bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) {
int numPlayers = 0;
// needs a square radius
const float radiusSq = cr::sqrf (radius);
// search the world for enemy players...
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent ()) {
continue;
}
if (client.ent->v.origin.distanceSq (location) < cr::sqrf (radius)) {
// don't target our teammates...
if (client.ent->v.origin.distanceSq (location) < radiusSq) {
if (client.team == m_team) {
return false;
return false; // don't target our teammates...
}
if (numPlayers++ > numEnemies) {
@ -1877,7 +1904,7 @@ void Bot::checkGrenadesThrow () {
};
// check if throwing a grenade is a good thing to do...
auto throwingCondition = game.mapIs(MapFlags::GrenadeWar)
const auto throwingCondition = game.mapIs (MapFlags::GrenadeWar)
? false
: (preventibleTasks
|| isInNarrowPlace ()
@ -1896,7 +1923,8 @@ void Bot::checkGrenadesThrow () {
// check again in some seconds
m_grenadeCheckTime = game.time () + kGrenadeCheckTime;
auto senseCondition = game.mapIs(MapFlags::GrenadeWar) ? false : !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy));
const auto senseCondition = game.mapIs (MapFlags::GrenadeWar) ? false : !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy));
if (!util.isAlive (m_lastEnemy) || senseCondition) {
clearThrowStates (m_states);
return;
@ -1939,7 +1967,7 @@ void Bot::checkGrenadesThrow () {
}
// enemy within a good throw distance?
auto grenadeToThrowCondition = game.mapIs(MapFlags::GrenadeWar)
const auto grenadeToThrowCondition = game.mapIs (MapFlags::GrenadeWar)
? 100.0f
: grenadeToThrow == Weapon::Smoke ? 200.0f : 400.0f;

View file

@ -77,17 +77,20 @@ int BotControl::cmdKickBots () {
}
int BotControl::cmdKillBots () {
enum args { alias = 1, team, max };
enum args { alias = 1, team, silent, max };
// do not issue any messages
bool silentKill = hasArg (silent) && strValue (silent).startsWith ("si");
// if team is specified, kick from specified tram
if (strValue (alias).endsWith ("_ct") || intValue (team) == 2 || strValue (team) == "ct") {
bots.killAllBots (Team::CT);
bots.killAllBots (Team::CT, silentKill);
}
else if (strValue (alias).endsWith ("_t") || intValue (team) == 1 || strValue (team) == "t") {
bots.killAllBots (Team::Terrorist);
bots.killAllBots (Team::Terrorist, silentKill);
}
else {
bots.killAllBots ();
bots.killAllBots (-1, silentKill);
}
return BotCommandResult::Handled;
}
@ -2138,7 +2141,7 @@ BotControl::BotControl () {
m_cmds.emplace ("add/addbot/add_ct/addbot_ct/add_t/addbot_t/addhs/addhs_t/addhs_ct", "add [difficulty] [personality] [team] [model] [name]", "Adding specific bot into the game.", &BotControl::cmdAddBot);
m_cmds.emplace ("kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot);
m_cmds.emplace ("removebots/kickbots/kickall/kickall_ct/kickall_t", "removebots [instant] [team]", "Kicks all the bots from the game.", &BotControl::cmdKickBots);
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team] [silent]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
m_cmds.emplace ("fill/fillserver", "fill [team] [count] [difficulty] [personality]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote);
m_cmds.emplace ("weapons/weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use", &BotControl::cmdWeaponMode);

View file

@ -86,6 +86,9 @@ void Game::levelInitialize (edict_t *entities, int max) {
// flush any print queue
ctrl.resetFlushTimestamp ();
// set the global timer function
timerStorage.setTimeAddress (&globals->time);
// go thru the all entities on map, and do whatever we're want
for (int i = 0; i < max; ++i) {
auto ent = entities + i;

View file

@ -953,11 +953,6 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) {
memcpy (&engfuncs, table, sizeof (enginefuncs_t));
globals = glob;
// set the global timer function
timerStorage.setTimeFunction ([] () {
return globals->time;
});
if (game.postload ()) {
return;
}

View file

@ -507,7 +507,7 @@ void BotManager::maintainAutoKill () {
// check if we're reached the delay, so kill out bots
if (!cr::fzero (m_autoKillCheckTime) && m_autoKillCheckTime < game.time ()) {
killAllBots ();
killAllBots (-1, true);
m_autoKillCheckTime = 0.0f;
return;
@ -679,7 +679,7 @@ void BotManager::kickFromTeam (Team team, bool removeAll) {
}
}
void BotManager::killAllBots (int team) {
void BotManager::killAllBots (int team, bool silent) {
// this function kills all bots on server (only this dll controlled bots)
for (const auto &bot : m_bots) {
@ -688,8 +688,11 @@ void BotManager::killAllBots (int team) {
}
bot->kill ();
}
if (!silent) {
ctrl.msg ("All bots died...");
}
}
void BotManager::kickBot (int index) {
auto bot = findBotByIndex (index);
@ -873,7 +876,10 @@ void BotManager::listBots () {
for (const auto &bot : bots) {
auto timelimitStr = cv_rotate_bots.bool_ () ? strings.format ("%-3.0f secs", bot->m_stayTime - game.time ()) : "unlimited";
ctrl.msg ("[%-2.1d]\t%-22.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%s", bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", botTeam (bot->m_team), bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr);
ctrl.msg ("[%-2.1d]\t%-22.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%s",
bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful",
botTeam (bot->m_team), bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_isAlive ? "yes" : "no", timelimitStr);
}
ctrl.msg ("%d bots", m_bots.length ());
}
@ -1003,11 +1009,16 @@ void BotManager::updateBotDifficulties () {
}
void BotManager::balanceBotDifficulties () {
// difficulty chaning once per round (time)
// difficulty changing once per round (time)
auto updateDifficulty = [] (Bot *bot, int32_t offset) {
bot->m_difficulty = cr::clamp (static_cast <Difficulty> (bot->m_difficulty + offset), Difficulty::Noob, Difficulty::Expert);
};
// with nightmare difficulty, there is no balance
if (cv_whose_your_daddy.bool_ ()) {
return;
}
if (cv_difficulty_auto.bool_ () && m_difficultyBalanceTime < game.time ()) {
const auto ratioPlayer = getAverageTeamKPD (false);
const auto ratioBots = getAverageTeamKPD (true);
@ -1343,7 +1354,7 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
// is this message about a bot who killed somebody?
if (killerBot != nullptr) {
killerBot->m_lastVictim = victim;
killerBot->setLastVictim (victim);
}
// did a human kill a bot on his team?
@ -1441,6 +1452,7 @@ void Bot::newRound () {
m_lastVictim = nullptr;
m_lastEnemy = nullptr;
m_lastEnemyOrigin = nullptr;
m_lastVictimOrigin = nullptr;
m_trackingEdict = nullptr;
m_timeNextTracking = 0.0f;
@ -1475,6 +1487,7 @@ void Bot::newRound () {
m_followWaitTime = 0.0f;
m_hostages.clear ();
m_forgetLastVictimTimer.invalidate ();
for (auto &timer : m_chatterTimes) {
timer = kMaxChatterRepeatInterval;
@ -1494,6 +1507,7 @@ void Bot::newRound () {
m_grenadeCheckTime = 0.0f;
m_isUsingGrenade = false;
m_bombSearchOverridden = false;
m_fireHurtsFriend = false;
m_blindButton = 0;
m_blindTime = 0.0f;

View file

@ -2820,7 +2820,7 @@ bool Bot::isBlockedRight () {
bool Bot::checkWallOnLeft () {
TraceResult tr {};
game.testLine (pev->origin, pev->origin + -pev->angles.right () * 45.0f, TraceIgnore::Monsters, ent (), &tr);
game.testLine (pev->origin, pev->origin - pev->angles.right () * 42.0f, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f) {
@ -2833,7 +2833,7 @@ bool Bot::checkWallOnRight () {
TraceResult tr {};
// do a trace to the right...
game.testLine (pev->origin, pev->origin + pev->angles.right () * 45.0f, TraceIgnore::Monsters, ent (), &tr);
game.testLine (pev->origin, pev->origin + pev->angles.right () * 42.0f, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f) {

View file

@ -8,6 +8,7 @@
#include <yapb.h>
ConVar cv_max_nodes_for_predict ("max_nodes_for_predict", "25", "Maximum number for path length, to predict the enemy.", true, 15.0f, 256.0f);
ConVar cv_whose_your_daddy ("whose_your_daddy", "0", "Enables or disables extra hard difficulty for bots.");
// game console variables
ConVar mp_flashlight ("mp_flashlight", nullptr, Var::GameRef);
@ -58,7 +59,7 @@ bool Bot::seesEntity (const Vector &dest, bool fromBody) {
return tr.flFraction >= 1.0f;
}
void Bot::updateAimDir () {
void Bot::setAimDirection () {
uint32_t flags = m_aimFlags;
// don't allow bot to look at danger positions under certain circumstances
@ -220,6 +221,11 @@ void Bot::updateAimDir () {
}
}
// 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 && !onLadder) {
m_lookAt.z = getEyesPos ().z;
@ -299,6 +305,15 @@ void Bot::updateLookAngles () {
return;
}
// just force directioon
if (m_difficulty == Difficulty::Expert && (m_aimFlags & AimFlags::Enemy) && (m_wantsToFire || usesSniper ()) && cv_whose_your_daddy.bool_ ()) {
pev->v_angle = direction;
pev->v_angle.clampAngles ();
updateBodyAngles ();
return;
}
const float aimSkill = cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 25.0f;
float accelerate = aimSkill * 30.0f;