aim: verify camp angles from nav data before using them
aim: tweaked a bit grenade handling, so bots should use them more aim: reduce time between selecting grenade and throwing it away aim: removed hacks in look angles code, due to removing yb_whoose_your_daddy cvar aim: use direct enemy origin from visibility check, and not re-calculate it aim: update enemy prediction, so it now depends on frame interval for a bot aim: additional height offset are tweaked, and now used only for difficulty 4 nav: tweaked a bit player avoidance code, and it's not preventing bot from checking terrain nav: do not check banned nodes, when bucket sizes re too low nav: cover nodes are now selected depending on total bots on server nav: let bot enter pause task after long jump nav: extend velocity by a little for a jump, like it was in first versions of bot nav: stuck checking is now taken in account lower minimal speed if bot is ducking fix: navigation reachability timers, so bots will have correct current node index while camping fix: bots are unable to finish pickup or destroy breakable task, if target is not reachable fix: cover nodes are now calculated as they should fix: manual calling bots add_[t/ct] now ignores yb_join_team cvar bot: tweaked a little difficulty levels, so level 4 is now nightmare level, and 3 is very heard bot: minor refactoring and moving functions to correct source file bot: add yb_economics_disrespect_percent, so bots can ignore economics and buy more different guns bot: add yb_check_darkness that allows to disable darkness checks for bot, thus disallowing usage of flashlight bot: camp buttons are now lightly depends on bot health chat: welcome chat message from bots is now sent during first freeze time period crlib: switch over to stdint.h and remove crlib-own types crlib: fixed alignment in sse code
This commit is contained in:
parent
722e4eda93
commit
29c00565dc
31 changed files with 1395 additions and 1305 deletions
504
src/combat.cpp
504
src/combat.cpp
|
|
@ -14,6 +14,7 @@ ConVar cv_check_enemy_invincibility ("yb_check_enemy_invincibility", "0", "Enabl
|
|||
ConVar cv_stab_close_enemies ("yb_stab_close_enemies", "1", "Enables or disables bot ability to stab the enemy with knife if bot is in good condition.");
|
||||
|
||||
ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::GameRef);
|
||||
ConVar sv_gravity ("sv_gravity", nullptr, Var::GameRef);
|
||||
|
||||
int Bot::numFriendsNear (const Vector &origin, float radius) {
|
||||
int count = 0;
|
||||
|
|
@ -133,17 +134,15 @@ bool Bot::checkBodyParts (edict_t *target) {
|
|||
}
|
||||
|
||||
// check top of head
|
||||
if (util.isPlayer (target)) {
|
||||
spot.z += 25.0f;
|
||||
game.testLine (eyes, spot, TraceIgnore::Everything, self, &result);
|
||||
spot.z += 25.0f;
|
||||
game.testLine (eyes, spot, TraceIgnore::Everything, self, &result);
|
||||
|
||||
if (result.flFraction >= 1.0f) {
|
||||
m_enemyParts |= Visibility::Head;
|
||||
m_enemyOrigin = result.vecEndPos;
|
||||
}
|
||||
if (result.flFraction >= 1.0f) {
|
||||
m_enemyParts |= Visibility::Head;
|
||||
m_enemyOrigin = result.vecEndPos;
|
||||
}
|
||||
|
||||
if (m_enemyParts != 0) {
|
||||
if (m_enemyParts != Visibility::None) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -197,10 +196,6 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (m_difficulty == Difficulty::Expert && m_kpdRatio < 1.5f && util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) {
|
||||
ignoreFOV = true;
|
||||
}
|
||||
|
||||
if ((ignoreFOV || isInViewCone (player->v.origin)) && isEnemyInFrustum (player) && checkBodyParts (player)) {
|
||||
m_seeEnemyTime = game.time ();
|
||||
m_lastEnemy = player;
|
||||
|
|
@ -276,7 +271,7 @@ bool Bot::lookupEnemies () {
|
|||
float scaleFactor = (1.0f / calculateScaleFactor (intresting));
|
||||
float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor;
|
||||
|
||||
if (distance < nearestDistance) {
|
||||
if (distance * 0.65f < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
newEnemy = intresting;
|
||||
}
|
||||
|
|
@ -309,7 +304,7 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
float distance = player->v.origin.distanceSq (pev->origin);
|
||||
|
||||
if (distance < nearestDistance) {
|
||||
if (distance * 0.65f < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
newEnemy = player;
|
||||
|
||||
|
|
@ -320,7 +315,7 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
}
|
||||
}
|
||||
m_enemyUpdateTime = cr::clamp (game.time () + getFrameInterval () * 25.0f, 0.5f, 0.75f);
|
||||
m_enemyUpdateTime = cr::clamp (game.time () + m_frameInterval * 25.0f, 0.5f, 0.75f);
|
||||
|
||||
if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) {
|
||||
newEnemy = shieldEnemy;
|
||||
|
|
@ -349,18 +344,7 @@ bool Bot::lookupEnemies () {
|
|||
pushRadioMessage (Radio::EnemySpotted);
|
||||
}
|
||||
m_targetEntity = nullptr; // stop following when we see an enemy...
|
||||
|
||||
if (m_difficulty == Difficulty::Expert) {
|
||||
m_enemySurpriseTime = m_actualReactionTime * 0.5f;
|
||||
}
|
||||
else {
|
||||
m_enemySurpriseTime = m_actualReactionTime;
|
||||
}
|
||||
|
||||
if (usesSniper ()) {
|
||||
m_enemySurpriseTime *= 0.5f;
|
||||
}
|
||||
m_enemySurpriseTime += game.time ();
|
||||
m_enemySurpriseTime = game.time () + m_actualReactionTime;
|
||||
|
||||
// zero out reaction time
|
||||
m_actualReactionTime = 0.0f;
|
||||
|
|
@ -461,7 +445,7 @@ Vector Bot::getBodyOffsetError (float distance) {
|
|||
}
|
||||
|
||||
if (m_aimErrorTime < game.time ()) {
|
||||
const float error = distance / (cr::clamp (static_cast <float> (m_difficulty), 1.0f, 3.0f) * 1000.0f);
|
||||
const float error = distance / (cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 1000.0f);
|
||||
auto &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins;
|
||||
|
||||
m_aimLastError = Vector (rg.get (mins.x * error, maxs.x * error), rg.get (mins.y * error, maxs.y * error), rg.get (mins.z * error, maxs.z * error));
|
||||
|
|
@ -474,10 +458,6 @@ const Vector &Bot::getEnemyBodyOffset () {
|
|||
// the purpose of this function, is to make bot aiming not so ideal. it's mutate m_enemyOrigin enemy vector
|
||||
// returned from visibility check function.
|
||||
|
||||
const auto headOffset = [] (edict_t *e) {
|
||||
return e->v.absmin.z + e->v.size.z * 0.81f;
|
||||
};
|
||||
|
||||
// if no visibility data, use last one
|
||||
if (!m_enemyParts) {
|
||||
return m_enemyOrigin;
|
||||
|
|
@ -485,7 +465,7 @@ const Vector &Bot::getEnemyBodyOffset () {
|
|||
float distance = m_enemy->v.origin.distance (pev->origin);
|
||||
|
||||
// do not aim at head, at long distance (only if not using sniper weapon)
|
||||
if ((m_enemyParts & Visibility::Body) && !usesSniper () && distance > (m_difficulty > Difficulty::Normal ? 2000.0f : 1000.0f)) {
|
||||
if ((m_enemyParts & Visibility::Body) && !usesSniper () && distance > (m_difficulty >= Difficulty::Normal ? 2000.0f : 1000.0f)) {
|
||||
m_enemyParts &= ~Visibility::Head;
|
||||
}
|
||||
|
||||
|
|
@ -493,18 +473,21 @@ const Vector &Bot::getEnemyBodyOffset () {
|
|||
else if (distance < 800.0f && usesSniper ()) {
|
||||
m_enemyParts &= ~Visibility::Head;
|
||||
}
|
||||
else if (distance < kSprayDistance / 2 && !usesPistol ()) {
|
||||
m_enemyParts &= ~Visibility::Head;
|
||||
}
|
||||
Vector aimPos = m_enemy->v.origin;
|
||||
|
||||
if (m_difficulty > Difficulty::Normal) {
|
||||
aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 2.0f);
|
||||
Vector spot = m_enemy->v.origin;
|
||||
Vector compensation = 1.0f * m_frameInterval * m_enemy->v.velocity - 1.0f * m_frameInterval * pev->velocity;
|
||||
|
||||
if (!usesSniper () && distance > kDoubleSprayDistance) {
|
||||
compensation *= m_frameInterval;
|
||||
compensation.z = 0.0f;
|
||||
}
|
||||
else {
|
||||
compensation = nullptr;
|
||||
}
|
||||
|
||||
// if we only suspect an enemy behind a wall take the worst skill
|
||||
if (!m_enemyParts && (m_states & Sense::SuspectEnemy)) {
|
||||
aimPos += getBodyOffsetError (distance);
|
||||
spot += getBodyOffsetError (distance);
|
||||
}
|
||||
else if (util.isPlayer (m_enemy)) {
|
||||
const float highOffset = m_kpdRatio < 1.0f ? 1.5f : 0.0f;
|
||||
|
|
@ -522,29 +505,29 @@ const Vector &Bot::getEnemyBodyOffset () {
|
|||
// now check is our skill match to aim at head, else aim at enemy body
|
||||
if (rg.chance (headshotPct)) {
|
||||
if (onLoosingStreak) {
|
||||
aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance);
|
||||
spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance));
|
||||
}
|
||||
else {
|
||||
aimPos = m_enemy->v.origin + m_enemy->v.view_ofs;
|
||||
spot = m_enemyOrigin;
|
||||
}
|
||||
}
|
||||
else {
|
||||
aimPos.z += highOffset;
|
||||
spot.z += highOffset;
|
||||
}
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Body) {
|
||||
aimPos.z += highOffset;
|
||||
spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance));
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Other) {
|
||||
aimPos = m_enemyOrigin;
|
||||
spot = m_enemyOrigin;
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Head) {
|
||||
aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance);
|
||||
spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance));
|
||||
}
|
||||
}
|
||||
|
||||
m_enemyOrigin = aimPos;
|
||||
m_lastEnemyOrigin = aimPos;
|
||||
m_enemyOrigin = spot + compensation;
|
||||
m_lastEnemyOrigin = spot + compensation;
|
||||
|
||||
// add some error to unskilled bots
|
||||
if (m_difficulty < Difficulty::Hard) {
|
||||
|
|
@ -559,19 +542,19 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
|
|||
};
|
||||
|
||||
static float offsetRanges[9][3] = {
|
||||
{ 0.0f, 0.0f, 0.0f }, // none
|
||||
{ 0.0f, 0.0f, 0.0f }, // melee
|
||||
{ 1.5f, 1.5f, -1.2f }, // pistol
|
||||
{ 6.5f, 0.0f, -9.9f }, // shotgun
|
||||
{ 0.5f, -8.5f, -11.0f }, // zoomrifle
|
||||
{ 0.5f, -8.5f, -11.5f }, // rifle
|
||||
{ 2.5f, 0.5f, -4.5f }, // smg
|
||||
{ 0.5f, 0.5f, 1.5f }, // sniper
|
||||
{ 1.5f, -2.0f, -12.0f } // heavy
|
||||
{ 0.0f, 0.0f, 0.0f }, // none
|
||||
{ 0.0f, 0.0f, 4.0f }, // melee
|
||||
{ 3.5f, 2.0f, 1.5f }, // pistol
|
||||
{ 9.5f, 5.0f, -8.0f }, // shotgun
|
||||
{ 4.5f -3.5f, -5.0f }, // zoomrifle
|
||||
{ 5.5f, -3.0f, -5.5f }, // rifle
|
||||
{ 5.5f, -2.5f, -5.5f }, // smg
|
||||
{ 3.5f, 1.5f, -6.0f }, // sniper
|
||||
{ 2.5f, -4.0f, -9.0f } // heavy
|
||||
};
|
||||
|
||||
// only highskilled bots do that
|
||||
if (m_difficulty < Difficulty::Normal) {
|
||||
if (m_difficulty != Difficulty::Expert) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
|
@ -710,7 +693,7 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) {
|
|||
}
|
||||
|
||||
if (numHits < 3 && thikness < 98) {
|
||||
if (dest.distanceSq (point) < 13143.0f) {
|
||||
if (dest.distanceSq (point) < cr::square (112.0f)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -774,19 +757,16 @@ bool Bot::needToPauseFiring (float distance) {
|
|||
}
|
||||
float offset = 4.25f;
|
||||
|
||||
if (distance < kSprayDistance / 4) {
|
||||
if (distance < kSprayDistance) {
|
||||
return false;
|
||||
}
|
||||
else if (distance < kSprayDistance) {
|
||||
offset = 10.0f;
|
||||
}
|
||||
else if (distance < kDoubleSprayDistance) {
|
||||
offset = 8.0f;
|
||||
}
|
||||
const float xPunch = cr::deg2rad (pev->punchangle.x);
|
||||
const float yPunch = cr::deg2rad (pev->punchangle.y);
|
||||
|
||||
const float interval = getFrameInterval ();
|
||||
const float interval = m_frameInterval;
|
||||
const float tolerance = (100.0f - static_cast <float> (m_difficulty) * 25.0f) / 99.0f;
|
||||
|
||||
// check if we need to compensate recoil
|
||||
|
|
@ -813,8 +793,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
|
||||
// select this weapon if it isn't already selected
|
||||
if (m_currentWeapon != id) {
|
||||
const auto &prop = conf.getWeaponProp (id);
|
||||
selectWeaponByName (prop.classname.chars ());
|
||||
selectWeaponById (id);
|
||||
|
||||
// reset burst fire variables
|
||||
m_firePause = 0.0f;
|
||||
|
|
@ -1072,7 +1051,7 @@ void Bot::focusEnemy () {
|
|||
// aim for the head and/or body
|
||||
m_lookAt = getEnemyBodyOffset ();
|
||||
|
||||
if (m_enemySurpriseTime > game.time ()) {
|
||||
if (m_enemySurpriseTime > game.time () && !usesKnife ()) {
|
||||
return;
|
||||
}
|
||||
float distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum?
|
||||
|
|
@ -1121,7 +1100,7 @@ void Bot::attackMovement () {
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_lastUsedNodesTime - getFrameInterval () > game.time ()) {
|
||||
if (m_lastUsedNodesTime - m_frameInterval > game.time ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1194,17 +1173,29 @@ void Bot::attackMovement () {
|
|||
else {
|
||||
m_fightStyle = Fight::Stay;
|
||||
}
|
||||
|
||||
// do not try to strafe while ducking
|
||||
if (isDucking () || isInNarrowPlace ()) {
|
||||
m_fightStyle = Fight::Stay;
|
||||
}
|
||||
m_lastFightStyleCheck = game.time ();
|
||||
}
|
||||
|
||||
if (m_fightStyle == Fight::Strafe) {
|
||||
if (m_strafeSetTime < game.time ()) {
|
||||
auto swapStrafeCombatDir = [&] () {
|
||||
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);
|
||||
};
|
||||
|
||||
// to start strafing, we have to first figure out if the target is on the left side or right side
|
||||
auto strafeUpdateTime = [] () {
|
||||
return game.time () + rg.get (0.5f, 1.0f);
|
||||
};
|
||||
|
||||
// to start strafing, we have to first figure out if the target is on the left side or right side
|
||||
if (m_strafeSetTime < game.time ()) {
|
||||
const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d ();
|
||||
const auto &rightSide = m_enemy->v.v_angle.right ().normalize2d ();
|
||||
|
||||
if ((dirToPoint | rightSide) < 0) {
|
||||
if ((dirToPoint | rightSide) < 0.0f) {
|
||||
m_combatStrafeDir = Dodge::Left;
|
||||
}
|
||||
else {
|
||||
|
|
@ -1212,31 +1203,43 @@ void Bot::attackMovement () {
|
|||
}
|
||||
|
||||
if (rg.chance (30)) {
|
||||
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);
|
||||
swapStrafeCombatDir ();
|
||||
}
|
||||
m_strafeSetTime = game.time () + rg.get (1.2f, 2.4f);
|
||||
m_strafeSetTime = strafeUpdateTime ();
|
||||
}
|
||||
|
||||
if (m_combatStrafeDir == Dodge::Right) {
|
||||
if (!checkWallOnLeft ()) {
|
||||
m_strafeSpeed = -pev->maxspeed;
|
||||
}
|
||||
else if (!checkWallOnRight ()) {
|
||||
swapStrafeCombatDir ();
|
||||
m_strafeSetTime = strafeUpdateTime ();
|
||||
|
||||
m_strafeSpeed = pev->maxspeed;
|
||||
}
|
||||
else {
|
||||
m_combatStrafeDir = Dodge::Left;
|
||||
m_strafeSetTime = game.time () + rg.get (0.8f, 1.2f);
|
||||
m_strafeSpeed = 0.0f;
|
||||
m_strafeSetTime = strafeUpdateTime ();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!checkWallOnRight ()) {
|
||||
m_strafeSpeed = pev->maxspeed;
|
||||
}
|
||||
else if (!checkWallOnLeft ()) {
|
||||
swapStrafeCombatDir ();
|
||||
m_strafeSetTime = strafeUpdateTime ();
|
||||
|
||||
m_strafeSpeed = -pev->maxspeed;
|
||||
}
|
||||
else {
|
||||
m_combatStrafeDir = Dodge::Right;
|
||||
m_strafeSetTime = game.time () + rg.get (0.8f, 1.2f);
|
||||
m_strafeSpeed = 0.0f;
|
||||
m_strafeSetTime = strafeUpdateTime ();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_difficulty >= Difficulty::Hard && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.get (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 150.0f) && !usesSniper ()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1244,16 +1247,19 @@ void Bot::attackMovement () {
|
|||
if ((m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) {
|
||||
int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
||||
|
||||
if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) {
|
||||
m_duckTime = game.time () + 0.64f;
|
||||
if (graph.isVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) {
|
||||
m_duckTime = game.time () + m_frameInterval * 2.0f;
|
||||
}
|
||||
}
|
||||
m_moveSpeed = 0.0f;
|
||||
m_strafeSpeed = 0.0f;
|
||||
m_navTimeset = game.time ();
|
||||
}
|
||||
|
||||
if (m_difficulty >= Difficulty::Hard && isOnFloor () && (m_duckTime < game.time ())) {
|
||||
m_navTimeset = game.time ();
|
||||
m_moveToGoal = false;
|
||||
m_checkTerrain = false;
|
||||
|
||||
if (m_difficulty >= Difficulty::Normal && isOnFloor () && m_duckTime < game.time ()) {
|
||||
if (distance < 768.0f) {
|
||||
if (rg.get (0, 1000) < rg.get (7, 12) && pev->velocity.length2d () > 150.0f && isInViewCone (m_enemy->v.origin)) {
|
||||
pev->button |= IN_JUMP;
|
||||
|
|
@ -1261,13 +1267,18 @@ void Bot::attackMovement () {
|
|||
}
|
||||
}
|
||||
|
||||
if (m_isReloading) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
m_duckTime = game.time () - 1.0f;
|
||||
}
|
||||
|
||||
if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) {
|
||||
Vector right, forward;
|
||||
pev->v_angle.angleVectors (&forward, &right, nullptr);
|
||||
|
||||
const auto &front = right * m_moveSpeed * 0.2f;
|
||||
const auto &side = right * m_strafeSpeed * 0.2f;
|
||||
const auto &spot = pev->origin + front + side + pev->velocity * getFrameInterval ();
|
||||
const auto &spot = pev->origin + front + side + pev->velocity * m_frameInterval;
|
||||
|
||||
if (isDeadlyMove (spot)) {
|
||||
m_strafeSpeed = -m_strafeSpeed;
|
||||
|
|
@ -1461,9 +1472,9 @@ void Bot::selectBestWeapon () {
|
|||
// this function chooses best weapon, from weapons that bot currently own, and change
|
||||
// current weapon to best one.
|
||||
|
||||
// if knife mode activated, force bot to use knife
|
||||
if (cv_jasonmode.bool_ ()) {
|
||||
// if knife mode activated, force bot to use knife
|
||||
selectWeaponByName ("weapon_knife");
|
||||
selectWeaponById (Weapon::Knife);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1505,11 +1516,11 @@ void Bot::selectBestWeapon () {
|
|||
chosenWeaponIndex %= kNumWeapons + 1;
|
||||
selectIndex = chosenWeaponIndex;
|
||||
|
||||
int id = tab[selectIndex].id;
|
||||
const int id = tab[selectIndex].id;
|
||||
|
||||
// select this weapon if it isn't already selected
|
||||
if (m_currentWeapon != id) {
|
||||
selectWeaponByName (tab[selectIndex].name);
|
||||
selectWeaponById (tab[selectIndex].id);
|
||||
}
|
||||
m_isReloading = false;
|
||||
m_reloadState = Reload::None;
|
||||
|
|
@ -1543,15 +1554,6 @@ int Bot::bestWeaponCarried () {
|
|||
return num;
|
||||
}
|
||||
|
||||
void Bot::selectWeaponByName (StringRef name) {
|
||||
issueCommand (name.chars ());
|
||||
}
|
||||
|
||||
void Bot::selectWeaponByIndex (int index) {
|
||||
auto tab = conf.getRawWeapons ();
|
||||
issueCommand (tab[index].name);
|
||||
}
|
||||
|
||||
void Bot::decideFollowUser () {
|
||||
// this function forces bot to follow user
|
||||
Array <edict_t *> users;
|
||||
|
|
@ -1685,7 +1687,7 @@ void Bot::checkReload () {
|
|||
|
||||
if (isLowOnAmmo (prop.id, 0.75f) && getAmmo (prop.id) > 0) {
|
||||
if (m_currentWeapon != prop.id) {
|
||||
selectWeaponByName (prop.classname);
|
||||
selectWeaponById (prop.id);
|
||||
}
|
||||
pev->button &= ~IN_ATTACK;
|
||||
|
||||
|
|
@ -1719,3 +1721,311 @@ float Bot::calculateScaleFactor (edict_t *ent) {
|
|||
|
||||
return entArea / botArea;
|
||||
}
|
||||
|
||||
Vector Bot::calcToss (const Vector &start, const Vector &stop) {
|
||||
// this function returns the velocity at which an object should looped from start to land near end.
|
||||
// returns null vector if toss is not feasible.
|
||||
|
||||
TraceResult tr {};
|
||||
float gravity = sv_gravity.float_ () * 0.55f;
|
||||
|
||||
Vector end = stop - pev->velocity;
|
||||
end.z -= 15.0f;
|
||||
|
||||
if (cr::abs (end.z - start.z) > 500.0f) {
|
||||
return nullptr;
|
||||
}
|
||||
Vector midPoint = start + (end - start) * 0.5f;
|
||||
game.testHull (midPoint, midPoint + Vector (0.0f, 0.0f, 500.0f), TraceIgnore::Monsters, head_hull, ent (), &tr);
|
||||
|
||||
if (tr.flFraction < 1.0f && tr.pHit) {
|
||||
midPoint = tr.vecEndPos;
|
||||
midPoint.z = tr.pHit->v.absmin.z - 1.0f;
|
||||
}
|
||||
|
||||
if (midPoint.z < start.z || midPoint.z < end.z) {
|
||||
return nullptr;
|
||||
}
|
||||
float timeOne = cr::sqrtf ((midPoint.z - start.z) / (0.5f * gravity));
|
||||
float timeTwo = cr::sqrtf ((midPoint.z - end.z) / (0.5f * gravity));
|
||||
|
||||
if (timeOne < 0.1f) {
|
||||
return nullptr;
|
||||
}
|
||||
Vector velocity = (end - start) / (timeOne + timeTwo);
|
||||
velocity.z = gravity * timeOne;
|
||||
|
||||
Vector apex = start + velocity * timeOne;
|
||||
apex.z = midPoint.z;
|
||||
|
||||
game.testHull (start, apex, TraceIgnore::None, head_hull, ent (), &tr);
|
||||
|
||||
if (tr.flFraction < 1.0f || tr.fAllSolid) {
|
||||
return nullptr;
|
||||
}
|
||||
game.testHull (end, apex, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
||||
|
||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||
float dot = -(tr.vecPlaneNormal | (apex - end).normalize ());
|
||||
|
||||
if (dot > 0.75f || tr.flFraction < 0.8f) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return velocity * 0.777f;
|
||||
}
|
||||
|
||||
Vector Bot::calcThrow (const Vector &start, const Vector &stop) {
|
||||
// this function returns the velocity vector at which an object should be thrown from start to hit end.
|
||||
// returns null vector if throw is not feasible.
|
||||
|
||||
Vector velocity = stop - start;
|
||||
TraceResult tr {};
|
||||
|
||||
float gravity = sv_gravity.float_ () * 0.55f;
|
||||
float time = velocity.length () / 195.0f;
|
||||
|
||||
if (time < 0.01f) {
|
||||
return nullptr;
|
||||
}
|
||||
else if (time > 2.0f) {
|
||||
time = 1.2f;
|
||||
}
|
||||
const float half = time * 0.5f;
|
||||
|
||||
velocity = velocity * (1.0f / time);
|
||||
velocity.z += gravity * half * half;
|
||||
|
||||
Vector apex = start + (stop - start) * 0.5f;
|
||||
apex.z += 0.5f * gravity * half;
|
||||
|
||||
game.testHull (start, apex, TraceIgnore::None, head_hull, ent (), &tr);
|
||||
|
||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||
return nullptr;
|
||||
}
|
||||
game.testHull (stop, apex, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
||||
|
||||
if (!cr::fequal (tr.flFraction, 1.0) || tr.fAllSolid) {
|
||||
float dot = -(tr.vecPlaneNormal | (apex - stop).normalize ());
|
||||
|
||||
if (dot > 0.75f || tr.flFraction < 0.8f) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return velocity * 0.7793f;
|
||||
}
|
||||
|
||||
edict_t *Bot::setCorrectGrenadeVelocity (const char *model) {
|
||||
edict_t *result = nullptr;
|
||||
|
||||
game.searchEntities ("classname", "grenade", [&] (edict_t *ent) {
|
||||
if (ent->v.owner == this->ent () && util.isModel (ent, model)) {
|
||||
result = ent;
|
||||
|
||||
// set the correct velocity for the grenade
|
||||
if (m_grenade.lengthSq () > 100.0f) {
|
||||
ent->v.velocity = m_grenade + (m_grenade * m_frameInterval * 4.0f);
|
||||
}
|
||||
m_grenadeCheckTime = game.time () + 3.0f;
|
||||
|
||||
selectBestWeapon ();
|
||||
completeTask ();
|
||||
|
||||
return EntitySearchResult::Break;
|
||||
}
|
||||
return EntitySearchResult::Continue;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void Bot::checkGrenadesThrow () {
|
||||
|
||||
// do not check cancel if we have grenade in out hands
|
||||
bool preventibleTasks = getCurrentTaskId () == Task::PlantBomb || getCurrentTaskId () == Task::DefuseBomb;
|
||||
|
||||
auto clearThrowStates = [] (uint32_t &states) {
|
||||
states &= ~(Sense::ThrowExplosive | Sense::ThrowFlashbang | Sense::ThrowSmoke);
|
||||
};
|
||||
|
||||
// check if throwing a grenade is a good thing to do...
|
||||
if (preventibleTasks || isInNarrowPlace () || cv_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || cv_jasonmode.bool_ () || (m_grenadeRequested || m_grenadeCheckTime >= game.time ())) {
|
||||
clearThrowStates (m_states);
|
||||
return;
|
||||
}
|
||||
|
||||
// check again in some seconds
|
||||
m_grenadeCheckTime = game.time () + kGrenadeCheckTime;
|
||||
|
||||
if (!util.isAlive (m_lastEnemy) || !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) {
|
||||
clearThrowStates (m_states);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we have grenades to throw
|
||||
int grenadeToThrow = bestGrenadeCarried ();
|
||||
|
||||
// if we don't have grenades no need to check it this round again
|
||||
if (grenadeToThrow == -1) {
|
||||
m_grenadeCheckTime = game.time () + 15.0f; // changed since, conzero can drop grens from dead players
|
||||
|
||||
clearThrowStates (m_states);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
int cancelProb = 20;
|
||||
|
||||
if (grenadeToThrow == Weapon::Flashbang) {
|
||||
cancelProb = 25;
|
||||
}
|
||||
else if (grenadeToThrow == Weapon::Smoke) {
|
||||
cancelProb = 35;
|
||||
}
|
||||
if (rg.chance (cancelProb)) {
|
||||
clearThrowStates (m_states);
|
||||
return;
|
||||
}
|
||||
}
|
||||
float distance = m_lastEnemyOrigin.distance2d (pev->origin);
|
||||
|
||||
// don't throw grenades at anything that isn't on the ground!
|
||||
if (!(m_lastEnemy->v.flags & FL_ONGROUND) && !m_lastEnemy->v.waterlevel && m_lastEnemyOrigin.z > pev->absmax.z) {
|
||||
distance = kInfiniteDistance;
|
||||
}
|
||||
|
||||
// too high to throw?
|
||||
if (m_lastEnemy->v.origin.z > pev->origin.z + 500.0f) {
|
||||
distance = kInfiniteDistance;
|
||||
}
|
||||
|
||||
// enemy within a good throw distance?
|
||||
if (!m_lastEnemyOrigin.empty () && distance > (grenadeToThrow == Weapon::Smoke ? 200.0f : 400.0f) && distance < 1200.0f) {
|
||||
bool allowThrowing = true;
|
||||
|
||||
// care about different grenades
|
||||
switch (grenadeToThrow) {
|
||||
case Weapon::Explosive:
|
||||
if (numFriendsNear (m_lastEnemy->v.origin, 256.0f) > 0) {
|
||||
allowThrowing = false;
|
||||
}
|
||||
else {
|
||||
float radius = m_lastEnemy->v.velocity.length2d ();
|
||||
const Vector &pos = (m_lastEnemy->v.velocity * 0.5f).get2d () + m_lastEnemy->v.origin;
|
||||
|
||||
if (radius < 164.0f) {
|
||||
radius = 164.0f;
|
||||
}
|
||||
auto predicted = graph.searchRadius (radius, pos, 12);
|
||||
|
||||
if (predicted.empty ()) {
|
||||
m_states &= ~Sense::ThrowExplosive;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto predict : predicted) {
|
||||
allowThrowing = true;
|
||||
|
||||
if (!graph.exists (predict)) {
|
||||
allowThrowing = false;
|
||||
continue;
|
||||
}
|
||||
m_throw = graph[predict].origin;
|
||||
|
||||
auto throwPos = calcThrow (getEyesPos (), m_throw);
|
||||
|
||||
if (throwPos.lengthSq () < 100.0f) {
|
||||
throwPos = calcToss (getEyesPos (), m_throw);
|
||||
}
|
||||
|
||||
if (throwPos.empty ()) {
|
||||
allowThrowing = false;
|
||||
}
|
||||
else {
|
||||
m_throw.z += 110.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowThrowing) {
|
||||
m_states |= Sense::ThrowExplosive;
|
||||
}
|
||||
else {
|
||||
m_states &= ~Sense::ThrowExplosive;
|
||||
}
|
||||
break;
|
||||
|
||||
case Weapon::Flashbang:
|
||||
{
|
||||
int nearest = graph.getNearest ((m_lastEnemy->v.velocity * 0.5f).get2d () + m_lastEnemy->v.origin);
|
||||
|
||||
if (nearest != kInvalidNodeIndex) {
|
||||
m_throw = graph[nearest].origin;
|
||||
|
||||
if (numFriendsNear (m_throw, 256.0f) > 0) {
|
||||
allowThrowing = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
allowThrowing = false;
|
||||
}
|
||||
|
||||
if (allowThrowing) {
|
||||
auto throwPos = calcThrow (getEyesPos (), m_throw);
|
||||
|
||||
if (throwPos.lengthSq () < 100.0f) {
|
||||
throwPos = calcToss (getEyesPos (), m_throw);
|
||||
}
|
||||
|
||||
if (throwPos.empty ()) {
|
||||
allowThrowing = false;
|
||||
}
|
||||
else {
|
||||
m_throw.z += 110.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowThrowing) {
|
||||
m_states |= Sense::ThrowFlashbang;
|
||||
}
|
||||
else {
|
||||
m_states &= ~Sense::ThrowFlashbang;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Weapon::Smoke:
|
||||
if (allowThrowing && !game.isNullEntity (m_lastEnemy)) {
|
||||
if (util.getShootingCone (m_lastEnemy, pev->origin) >= 0.9f) {
|
||||
allowThrowing = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowThrowing) {
|
||||
m_states |= Sense::ThrowSmoke;
|
||||
}
|
||||
else {
|
||||
m_states &= ~Sense::ThrowSmoke;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
clearThrowStates (m_states);
|
||||
return;
|
||||
}
|
||||
const float kMaxThrowTime = game.time () + kGrenadeCheckTime * 6.0f;
|
||||
|
||||
if (m_states & Sense::ThrowExplosive) {
|
||||
startTask (Task::ThrowExplosive, TaskPri::Throw, kInvalidNodeIndex, kMaxThrowTime, false);
|
||||
}
|
||||
else if (m_states & Sense::ThrowFlashbang) {
|
||||
startTask (Task::ThrowFlashbang, TaskPri::Throw, kInvalidNodeIndex, kMaxThrowTime, false);
|
||||
}
|
||||
else if (m_states & Sense::ThrowSmoke) {
|
||||
startTask (Task::ThrowSmoke, TaskPri::Throw, kInvalidNodeIndex, kMaxThrowTime, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
clearThrowStates (m_states);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue