graph: reworked buckets so they can handle very large number of nodes
graph: reworked buckets so they can handle very large number of nodes aim: bots should more respect headshot allow option (needs testing) aim: incorporated never-finished changes from pr #204 nav: increased reachability timers a bit nav: ensure buckets has enough nodes before use they conf: introduced max recoil in difficulty config file bot: overall fixes to jason mode, treat knife in hands and no weapons as jason mode too bot: changed default difficulty level for bots to level 3 fix: knife attacks not working since last commit (fixes #429) fix: hostage rescue not working since last commit (fixes #427) refactor: use range loops for graph outside graph class when possible
This commit is contained in:
parent
3232c5a8b0
commit
1a650c57ce
14 changed files with 426 additions and 347 deletions
|
|
@ -19,11 +19,12 @@
|
|||
; headshotProbability - Probability bot should aim at head instead of body if body and head both visible.
|
||||
; seenThruWallChance - Chance the bot will attack enemy if he believes he's there and just seen him.
|
||||
; heardThruWallChance - Chance the bot will attack enemy if he believes he's there and just heard him.
|
||||
; maxWeaponRecoil - Maximum weapon recoil to compensate by pausing fire.
|
||||
; aimError - (x, y, z) offsets to add aim error to bot aiming
|
||||
;
|
||||
|
||||
Noob = 0.8, 1.0, 5, 0, 0
|
||||
Easy = 0.6, 0.8, 30, 10, 10
|
||||
Normal = 0.4, 0.6, 50, 30, 40
|
||||
Hard = 0.2, 0.4, 75, 60, 70
|
||||
Expert = 0.1, 0.2, 100, 90, 90
|
||||
|
||||
Noob = 0.8, 1.0, 5, 0, 0, 38, 30.0, 30.0, 40.0
|
||||
Easy = 0.6, 0.8, 10, 0, 10, 32, 15.0, 15.0, 24.0
|
||||
Normal = 0.4, 0.6, 50, 30, 40, 26, 5.0, 5.0, 10.0
|
||||
Hard = 0.2, 0.4, 75, 60, 70, 23, 0.0, 0.0, 0.0
|
||||
Expert = 0.1, 0.2, 100, 90, 90, 21, 0.0, 0.0, 0.0
|
||||
|
|
|
|||
|
|
@ -60,6 +60,13 @@ yb_radio_mode "2"
|
|||
//
|
||||
yb_economics_rounds "1"
|
||||
|
||||
//
|
||||
// Allows bots to ignore economics and buy weapons with disrespect of economics.
|
||||
// ---
|
||||
// Default: "25", Min: "0", Max: "100"
|
||||
//
|
||||
yb_economics_disrespect_percent "25"
|
||||
|
||||
//
|
||||
// Specifies whether bots able to use 'shift' if they thinks that enemy is near.
|
||||
// ---
|
||||
|
|
@ -81,6 +88,13 @@ yb_camping_allowed "1"
|
|||
//
|
||||
yb_avoid_grenades "1"
|
||||
|
||||
//
|
||||
// Allows or disallows bot to check environment for darkness, thus allows or not to use flashlights or NVG.
|
||||
// ---
|
||||
// Default: "1", Min: "0", Max: "1"
|
||||
//
|
||||
yb_check_darkness "1"
|
||||
|
||||
//
|
||||
// Lower bound of time from which time for camping is calculated
|
||||
// ---
|
||||
|
|
@ -193,6 +207,13 @@ yb_ignore_objectives "0"
|
|||
//
|
||||
yb_random_knife_attacks "1"
|
||||
|
||||
//
|
||||
// Maximum number for path length, to predict the enemy.
|
||||
// ---
|
||||
// Default: "30", Min: "15", Max: "256"
|
||||
//
|
||||
yb_max_nodes_for_predict "30"
|
||||
|
||||
//
|
||||
// Enables or disables bots chat functionality.
|
||||
// ---
|
||||
|
|
@ -210,7 +231,7 @@ 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: "2"
|
||||
// Default: "2", Min: "0", Max: "3"
|
||||
//
|
||||
yb_shoots_thru_walls "2"
|
||||
|
||||
|
|
@ -407,9 +428,9 @@ yb_name_prefix ""
|
|||
//
|
||||
// All bots difficulty level. Changing at runtime will affect already created bots.
|
||||
// ---
|
||||
// Default: "4", Min: "0", Max: "4"
|
||||
// Default: "3", Min: "0", Max: "4"
|
||||
//
|
||||
yb_difficulty "4"
|
||||
yb_difficulty "3"
|
||||
|
||||
//
|
||||
// Lower bound of random difficulty on bot creation. Only affects newly created bots. -1 means yb_difficulty only used.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public:
|
|||
int32_t headshotPct {};
|
||||
int32_t seenThruPct {};
|
||||
int32_t hearThruPct {};
|
||||
int32_t maxRecoil {};
|
||||
Vector aimError {};
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -151,6 +151,9 @@ public:
|
|||
// test line
|
||||
void testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr);
|
||||
|
||||
// test model
|
||||
void testModel (const Vector &start, const Vector &end, int hullNumber, edict_t *entToHit, TraceResult *ptr);
|
||||
|
||||
// trace line with channel, but allows us to store last traceline bot has fired, saving us some cpu cycles
|
||||
bool testLineChannel (TraceChannel channel, const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult &result);
|
||||
|
||||
|
|
|
|||
30
inc/graph.h
30
inc/graph.h
|
|
@ -259,10 +259,6 @@ public:
|
|||
friend class Bot;
|
||||
|
||||
private:
|
||||
struct Bucket {
|
||||
int x, y, z;
|
||||
};
|
||||
|
||||
int m_editFlags {};
|
||||
int m_loadAttempts {};
|
||||
int m_cacheNodeIndex {};
|
||||
|
|
@ -297,12 +293,13 @@ private:
|
|||
IntArray m_rescuePoints {};
|
||||
IntArray m_visitedGoals {};
|
||||
|
||||
SmallArray <int32_t> m_buckets[kMaxBucketsInsidePos][kMaxBucketsInsidePos][kMaxBucketsInsidePos];
|
||||
SmallArray <Matrix> m_matrix {};
|
||||
SmallArray <Practice> m_practice {};
|
||||
SmallArray <Path> m_paths {};
|
||||
SmallArray <uint8_t> m_vistable {};
|
||||
|
||||
HashMap <int32_t, Array <int32_t>, EmptyHash <int32_t>> m_hashTable;
|
||||
|
||||
String m_graphAuthor {};
|
||||
String m_graphModified {};
|
||||
|
||||
|
|
@ -327,6 +324,7 @@ public:
|
|||
int getPathDist (int srcIndex, int destIndex);
|
||||
int clearConnections (int index);
|
||||
int getBspSize ();
|
||||
int locateBucket (const Vector &pos);
|
||||
|
||||
float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin);
|
||||
|
||||
|
|
@ -394,9 +392,8 @@ public:
|
|||
const char *getDataDirectory (bool isMemoryFile = false);
|
||||
const char *getOldFormatGraphName (bool isMemoryFile = false);
|
||||
|
||||
Bucket locateBucket (const Vector &pos);
|
||||
IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1);
|
||||
const SmallArray <int32_t> &getNodesInBucket (const Vector &pos);
|
||||
IntArray getNarestInRadius (float radius, const Vector &origin, int maxCount = -1);
|
||||
const IntArray &getNodesInBucket (const Vector &pos);
|
||||
|
||||
public:
|
||||
size_t getMaxRouteLength () const {
|
||||
|
|
@ -468,6 +465,23 @@ public:
|
|||
edict_t *getEditor () {
|
||||
return m_editor;
|
||||
}
|
||||
|
||||
public:
|
||||
Path *begin () {
|
||||
return m_paths.begin ();
|
||||
}
|
||||
|
||||
Path *begin () const {
|
||||
return m_paths.begin ();
|
||||
}
|
||||
|
||||
Path *end () {
|
||||
return m_paths.end ();
|
||||
}
|
||||
|
||||
Path *end () const {
|
||||
return m_paths.end ();
|
||||
}
|
||||
};
|
||||
|
||||
// we're need `bots`
|
||||
|
|
|
|||
12
inc/yapb.h
12
inc/yapb.h
|
|
@ -467,10 +467,6 @@ constexpr int kGameMaxPlayers = 32;
|
|||
constexpr int kGameTeamNum = 2;
|
||||
constexpr int kInvalidNodeIndex = -1;
|
||||
|
||||
constexpr int kMaxBucketSize = static_cast <int> (kMaxNodes * 0.65);
|
||||
constexpr int kMaxBucketsInsidePos = kMaxNodes * 8 / kMaxBucketSize + 1;
|
||||
constexpr int kMaxNodesInsideBucket = kMaxBucketSize / kMaxBucketsInsidePos;
|
||||
|
||||
// weapon masks
|
||||
constexpr auto kPrimaryWeaponMask = (cr::bit (Weapon::XM1014) | cr::bit (Weapon::M3) | cr::bit (Weapon::MAC10) | cr::bit (Weapon::UMP45) | cr::bit (Weapon::MP5) | cr::bit (Weapon::TMP) | cr::bit (Weapon::P90) | cr::bit (Weapon::AUG) | cr::bit (Weapon::M4A1) | cr::bit (Weapon::SG552) | cr::bit (Weapon::AK47) | cr::bit (Weapon::Scout) | cr::bit (Weapon::SG550) | cr::bit (Weapon::AWP) | cr::bit (Weapon::G3SG1) | cr::bit (Weapon::M249) | cr::bit (Weapon::Famas) | cr::bit (Weapon::Galil));
|
||||
constexpr auto kSecondaryWeaponMask = (cr::bit (Weapon::P228) | cr::bit (Weapon::Elite) | cr::bit (Weapon::USP) | cr::bit (Weapon::Glock18) | cr::bit (Weapon::Deagle) | cr::bit (Weapon::FiveSeven));
|
||||
|
|
@ -690,6 +686,7 @@ private:
|
|||
float m_changeViewTime {}; // timestamp to change look at while at freezetime
|
||||
float m_breakableTime {}; // breakeble acquired time
|
||||
float m_jumpDistance {}; // last jump distance
|
||||
float m_lastBadWeaponSwitchTime {}; // last time we're switched weapon as it's bad
|
||||
|
||||
bool m_moveToGoal {}; // bot currently moving to goal??
|
||||
bool m_isStuck {}; // bot is stuck
|
||||
|
|
@ -709,6 +706,7 @@ private:
|
|||
bool m_moveToC4 {}; // ct is moving to bomb
|
||||
bool m_grenadeRequested {}; // bot requested change to grenade
|
||||
bool m_needToSendWelcomeChat {}; // bot needs to greet people on server?
|
||||
bool m_switchedToKnifeDuringJump {}; // bot needs to revert weapon after jump?
|
||||
|
||||
Pickup m_pickupType {}; // type of entity which needs to be used/picked up
|
||||
PathWalk m_pathWalk {}; // pointer to current node from path
|
||||
|
|
@ -778,7 +776,6 @@ private:
|
|||
float getEstimatedNodeReachTime ();
|
||||
float isInFOV (const Vector &dest);
|
||||
float getShiftSpeed ();
|
||||
float getEnemyBodyOffsetCorrection (float distance);
|
||||
float calculateScaleFactor (edict_t *ent);
|
||||
|
||||
bool canReplaceWeapon ();
|
||||
|
|
@ -809,6 +806,7 @@ private:
|
|||
bool reactOnEnemy ();
|
||||
bool selectBestNextNode ();
|
||||
bool hasAnyWeapons ();
|
||||
bool isKnifeMode ();
|
||||
bool isDeadlyMove (const Vector &to);
|
||||
bool isOutOfBombTimer ();
|
||||
bool isWeaponBadAtDistance (int weaponIndex, float distance);
|
||||
|
|
@ -830,6 +828,7 @@ private:
|
|||
bool updateLiftHandling ();
|
||||
bool updateLiftStates ();
|
||||
bool canRunHeavyWeight ();
|
||||
bool isEnemyInSight (Vector &endPos);
|
||||
|
||||
void doPlayerAvoidance (const Vector &normal);
|
||||
void selectCampButtons (int index);
|
||||
|
|
@ -920,12 +919,13 @@ private:
|
|||
edict_t *lookupBreakable ();
|
||||
edict_t *setCorrectGrenadeVelocity (const char *model);
|
||||
|
||||
const Vector &getEnemyBodyOffset ();
|
||||
Vector getEnemyBodyOffset ();
|
||||
Vector calcThrow (const Vector &start, const Vector &stop);
|
||||
Vector calcToss (const Vector &start, const Vector &stop);
|
||||
Vector isBombAudible ();
|
||||
Vector getBodyOffsetError (float distance);
|
||||
Vector getCampDirection (const Vector &dest);
|
||||
Vector getCustomHeight (float distance);
|
||||
|
||||
uint8_t computeMsec ();
|
||||
|
||||
|
|
|
|||
102
src/botlib.cpp
102
src/botlib.cpp
|
|
@ -44,6 +44,8 @@ ConVar cv_pickup_best ("yb_pickup_best", "1", "Allows or disallows bots to picku
|
|||
ConVar cv_ignore_objectives ("yb_ignore_objectives", "0", "Allows or disallows bots to do map objectives, i.e. plant/defuse bombs, and saves hostages.");
|
||||
ConVar cv_random_knife_attacks ("yb_random_knife_attacks", "1", "Allows or disallows the ability for random knife attacks when bot is rushing and no enemy is nearby.");
|
||||
|
||||
ConVar cv_max_nodes_for_predict ("yb_max_nodes_for_predict", "30", "Maximum number for path length, to predict the enemy.", true, 15.0f, 256.0f);
|
||||
|
||||
// game console variables
|
||||
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::GameRef);
|
||||
ConVar mp_flashlight ("mp_flashlight", nullptr, Var::GameRef);
|
||||
|
|
@ -327,7 +329,7 @@ void Bot::updatePickups () {
|
|||
// this function finds Items to collect or use in the near of a bot
|
||||
|
||||
// don't try to pickup anything while on ladder or trying to escape from bomb...
|
||||
if (isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || m_seeEnemyTime + 3.0f > game.time () || !game.isNullEntity (m_enemy) || !bots.hasIntrestingEntities ()) {
|
||||
if (isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || m_seeEnemyTime + 3.0f < game.time () || !game.isNullEntity (m_enemy) || !bots.hasIntrestingEntities ()) {
|
||||
m_pickupItem = nullptr;
|
||||
m_pickupType = Pickup::None;
|
||||
|
||||
|
|
@ -758,9 +760,14 @@ Vector Bot::getCampDirection (const Vector &dest) {
|
|||
}
|
||||
|
||||
if (graph.exists (lookAtWaypoint)) {
|
||||
return graph[lookAtWaypoint].origin + pev->view_ofs;
|
||||
return graph[lookAtWaypoint].origin;
|
||||
}
|
||||
}
|
||||
auto dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
||||
|
||||
if (graph.exists (dangerIndex)) {
|
||||
return graph[dangerIndex].origin;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -1548,15 +1555,6 @@ void Bot::updateEmotions () {
|
|||
}
|
||||
|
||||
void Bot::overrideConditions () {
|
||||
if (!usesKnife () && m_difficulty >= Difficulty::Normal && ((m_aimFlags & AimFlags::Enemy) || (m_states & Sense::SeeingEnemy)) && !cv_jasonmode.bool_ () && getCurrentTaskId () != Task::Camp && getCurrentTaskId () != Task::SeekCover && !isOnLadder ()) {
|
||||
m_moveToGoal = false; // don't move to goal
|
||||
m_navTimeset = game.time ();
|
||||
|
||||
if (util.isPlayer (m_enemy) || (cv_attack_monsters.bool_ () && util.isMonster (m_enemy))) {
|
||||
attackMovement ();
|
||||
}
|
||||
}
|
||||
|
||||
// check if we need to escape from bomb
|
||||
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_notKilled && getCurrentTaskId () != Task::EscapeFromBomb && getCurrentTaskId () != Task::Camp && isOutOfBombTimer ()) {
|
||||
completeTask (); // complete current task
|
||||
|
|
@ -1566,7 +1564,7 @@ void Bot::overrideConditions () {
|
|||
}
|
||||
|
||||
// special handling, if we have a knife in our hands
|
||||
if ((bots.getRoundStartTime () + 6.0f > game.time () || !hasAnyWeapons ()) && usesKnife () && (util.isPlayer (m_enemy) || (cv_attack_monsters.bool_ () && util.isMonster (m_enemy)))) {
|
||||
if ((usesKnife () || !hasAnyWeapons ()) && (util.isPlayer (m_enemy) || (cv_attack_monsters.bool_ () && util.isMonster (m_enemy)))) {
|
||||
float length = pev->origin.distance2d (m_enemy->v.origin);
|
||||
|
||||
// do waypoint movement if enemy is not reachable with a knife
|
||||
|
|
@ -1574,15 +1572,20 @@ void Bot::overrideConditions () {
|
|||
int nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin);
|
||||
|
||||
if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex && cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) {
|
||||
float taskTime = game.time () + length / pev->maxspeed * 0.5f;
|
||||
|
||||
if (getCurrentTaskId () != Task::MoveToPosition && !cr::fequal (getTask ()->desire, TaskPri::Hide)) {
|
||||
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true);
|
||||
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + length / (m_moveSpeed * 2.0f), true);
|
||||
}
|
||||
m_isEnemyReachable = false;
|
||||
m_enemy = nullptr;
|
||||
|
||||
m_enemyIgnoreTimer = taskTime;
|
||||
else {
|
||||
if (getCurrentTaskId () == Task::MoveToPosition && getTask ()->data != nearestToEnemyPoint) {
|
||||
clearTask (Task::MoveToPosition);
|
||||
startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, game.time () + length / (m_moveSpeed * 2.0f), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (length <= 100.0f && (m_states & Sense::SeeingEnemy) && getCurrentTaskId () == Task::MoveToPosition) {
|
||||
clearTask (Task::MoveToPosition); // remove any move tasks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1596,7 +1599,7 @@ void Bot::overrideConditions () {
|
|||
}
|
||||
|
||||
// special handling for reloading
|
||||
if (m_reloadState != Reload::None && m_isReloading && ((pev->button | m_oldButtons) & IN_RELOAD)) {
|
||||
if (m_reloadState != Reload::None && m_isReloading) {
|
||||
if (m_seeEnemyTime + 4.0f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) {
|
||||
m_moveSpeed = 0.0f;
|
||||
m_strafeSpeed = 0.0f;
|
||||
|
|
@ -1714,7 +1717,7 @@ void Bot::setConditions () {
|
|||
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)) {
|
||||
if (graph.exists (nodeIndex) && pathLength < cv_max_nodes_for_predict.int_ () && pev->origin.distanceSq (graph[nodeIndex].origin) > cr::square (384.0f)) {
|
||||
m_aimFlags |= AimFlags::PredictPath;
|
||||
}
|
||||
}
|
||||
|
|
@ -2655,7 +2658,7 @@ void Bot::updateAimDir () {
|
|||
auto pathLength = 0;
|
||||
auto aimNode = findAimingNode (m_lastEnemyOrigin, pathLength);
|
||||
|
||||
if (graph.exists (aimNode) && pathLength < kMaxBucketsInsidePos * 2) {
|
||||
if (graph.exists (aimNode) && pathLength < cv_max_nodes_for_predict.int_ ()) {
|
||||
m_lookAt = graph[aimNode].origin;
|
||||
m_lookAtSafe = m_lookAt;
|
||||
|
||||
|
|
@ -2688,7 +2691,7 @@ void Bot::updateAimDir () {
|
|||
m_lookAt = m_destOrigin;
|
||||
}
|
||||
}
|
||||
else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()){
|
||||
else if (m_seeEnemyTime + 3.0f > game.time () && !m_lastEnemyOrigin.empty ()) {
|
||||
m_lookAt = m_lastEnemyOrigin;;
|
||||
}
|
||||
else {
|
||||
|
|
@ -2821,6 +2824,12 @@ void Bot::frame () {
|
|||
kick ();
|
||||
return;
|
||||
}
|
||||
|
||||
// clear enemy far away
|
||||
if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && pev->origin.distanceSq (m_lastEnemyOrigin) >= cr::square (2048.0)) {
|
||||
m_lastEnemy = nullptr;
|
||||
m_lastEnemyOrigin = nullptr;
|
||||
}
|
||||
m_slowFrameTimestamp = game.time () + 0.5f;
|
||||
}
|
||||
|
||||
|
|
@ -2839,7 +2848,7 @@ void Bot::update () {
|
|||
if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) {
|
||||
m_hasC4 = !!(pev->weapons & cr::bit (Weapon::C4));
|
||||
|
||||
if (m_hasC4 && cv_ignore_objectives.bool_ ()) {
|
||||
if (m_hasC4 && (cv_ignore_objectives.bool_ () || cv_jasonmode.bool_ ())) {
|
||||
m_hasC4 = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -2937,7 +2946,7 @@ void Bot::normal_ () {
|
|||
}
|
||||
|
||||
// bots rushing with knife, when have no enemy (thanks for idea to nicebot project)
|
||||
if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) {
|
||||
if (cv_random_knife_attacks.bool_ () && usesKnife () && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasHostage () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) {
|
||||
if (rg.chance (40)) {
|
||||
pev->button |= IN_ATTACK;
|
||||
}
|
||||
|
|
@ -2976,7 +2985,7 @@ void Bot::normal_ () {
|
|||
}
|
||||
|
||||
// reached waypoint is a camp waypoint
|
||||
if ((m_pathFlags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && cv_camping_allowed.bool_ ()) {
|
||||
if ((m_pathFlags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && cv_camping_allowed.bool_ () && !isKnifeMode ()) {
|
||||
|
||||
// check if bot has got a primary weapon and hasn't camped before
|
||||
if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.time () && !hasHostage ()) {
|
||||
|
|
@ -3129,13 +3138,13 @@ void Bot::normal_ () {
|
|||
}
|
||||
}
|
||||
else {
|
||||
if (!isDucking () && !cr::fequal (m_minSpeed, pev->maxspeed) && m_minSpeed > 1.0f) {
|
||||
if (!isDucking () && !usesKnife () && !cr::fequal (m_minSpeed, pev->maxspeed) && m_minSpeed > 1.0f) {
|
||||
m_moveSpeed = m_minSpeed;
|
||||
}
|
||||
}
|
||||
float shiftSpeed = getShiftSpeed ();
|
||||
|
||||
if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty >= Difficulty::Normal && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !cv_jasonmode.bool_ () && !bots.isBombPlanted ()) {
|
||||
if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (cv_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty >= Difficulty::Normal && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !isKnifeMode () && !bots.isBombPlanted ()) {
|
||||
m_moveSpeed = shiftSpeed;
|
||||
}
|
||||
|
||||
|
|
@ -3248,7 +3257,7 @@ void Bot::huntEnemy_ () {
|
|||
}
|
||||
|
||||
// bots skill higher than 60?
|
||||
if (cv_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty >= Difficulty::Normal && !cv_jasonmode.bool_ ()) {
|
||||
if (cv_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty >= Difficulty::Normal && !isKnifeMode ()) {
|
||||
// then make him move slow if near enemy
|
||||
if (!(m_currentTravelFlags & PathFlag::Jump)) {
|
||||
if (m_currentNodeIndex != kInvalidNodeIndex) {
|
||||
|
|
@ -3359,8 +3368,8 @@ void Bot::attackEnemy_ () {
|
|||
ignoreCollision ();
|
||||
attackMovement ();
|
||||
|
||||
if (usesKnife () && !m_lastEnemyOrigin.empty ()) {
|
||||
m_destOrigin = m_lastEnemyOrigin;
|
||||
if (usesKnife () && !m_enemyOrigin.empty ()) {
|
||||
m_destOrigin = m_enemyOrigin;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -3939,7 +3948,7 @@ void Bot::followUser_ () {
|
|||
}
|
||||
m_aimFlags |= AimFlags::Nav;
|
||||
|
||||
if (cv_walking_allowed.bool_ () && m_targetEntity->v.maxspeed < m_moveSpeed && !cv_jasonmode.bool_ ()) {
|
||||
if (cv_walking_allowed.bool_ () && m_targetEntity->v.maxspeed < m_moveSpeed && !isKnifeMode ()) {
|
||||
m_moveSpeed = getShiftSpeed ();
|
||||
}
|
||||
|
||||
|
|
@ -3957,7 +3966,7 @@ void Bot::followUser_ () {
|
|||
clearSearchNodes ();
|
||||
|
||||
int destIndex = graph.getNearest (m_targetEntity->v.origin);
|
||||
auto points = graph.searchRadius (200.0f, m_targetEntity->v.origin);
|
||||
auto points = graph.getNarestInRadius (200.0f, m_targetEntity->v.origin);
|
||||
|
||||
for (auto &newIndex : points) {
|
||||
// if waypoint not yet used, assign it as dest
|
||||
|
|
@ -4268,15 +4277,15 @@ void Bot::escapeFromBomb_ () {
|
|||
int lastSelectedGoal = kInvalidNodeIndex, minPathDistance = kInfiniteDistanceLong;
|
||||
float safeRadius = rg.get (1513.0f, 2048.0f);
|
||||
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
if (graph[i].origin.distance (graph.getBombOrigin ()) < safeRadius || isOccupiedNode (i)) {
|
||||
for (const auto &path : graph) {
|
||||
if (path.origin.distance (graph.getBombOrigin ()) < safeRadius || isOccupiedNode (path.number)) {
|
||||
continue;
|
||||
}
|
||||
int pathDistance = graph.getPathDist (m_currentNodeIndex, i);
|
||||
int pathDistance = graph.getPathDist (m_currentNodeIndex, path.number);
|
||||
|
||||
if (minPathDistance > pathDistance) {
|
||||
minPathDistance = pathDistance;
|
||||
lastSelectedGoal = i;
|
||||
lastSelectedGoal = path.number;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4606,8 +4615,7 @@ void Bot::checkSpawnConditions () {
|
|||
}
|
||||
|
||||
if (m_difficulty >= Difficulty::Normal && rg.chance (m_personality == Personality::Rusher ? 99 : 50) && !m_isReloading && game.mapIs (MapFlags::HostageRescue | MapFlags::Demolition | MapFlags::Escape | MapFlags::Assassination)) {
|
||||
if (cv_jasonmode.bool_ ()) {
|
||||
selectSecondary ();
|
||||
if (isKnifeMode ()) {
|
||||
dropCurrentWeapon ();
|
||||
}
|
||||
else {
|
||||
|
|
@ -4745,7 +4753,7 @@ void Bot::logic () {
|
|||
updateLookAngles (); // and turn to chosen aim direction
|
||||
|
||||
// the bots wants to fire at something?
|
||||
if (m_shootAtDeadTime > game.time () || (m_wantsToFire && !m_isUsingGrenade && (usesKnife () || m_shootTime <= game.time ()))) {
|
||||
if (m_shootAtDeadTime > game.time () || (m_wantsToFire && !m_isUsingGrenade && (m_shootTime <= game.time ()))) {
|
||||
fireWeapons (); // if bot didn't fire a bullet try again next frame
|
||||
}
|
||||
|
||||
|
|
@ -4794,7 +4802,7 @@ void Bot::logic () {
|
|||
}
|
||||
|
||||
// ensure we're not stuck destroying/picking something
|
||||
if (m_navTimeset + getEstimatedNodeReachTime () - m_frameInterval * 2.0f < game.time () && !(m_states & Sense::SeeingEnemy)) {
|
||||
if (m_navTimeset + getEstimatedNodeReachTime () + 2.0f < game.time () && !(m_states & Sense::SeeingEnemy)) {
|
||||
ensureEntitiesClear ();
|
||||
}
|
||||
translateInput ();
|
||||
|
|
@ -4848,6 +4856,10 @@ void Bot::showDebugOverlay () {
|
|||
static HashMap <int32_t, String> personalities;
|
||||
static HashMap <int32_t, String> flags;
|
||||
|
||||
auto boolValue = [] (const bool test) {
|
||||
return test ? "Yes" : "No";
|
||||
};
|
||||
|
||||
if (tasks.empty ()) {
|
||||
tasks[Task::Normal] = "Normal";
|
||||
tasks[Task::Pause] = "Pause";
|
||||
|
|
@ -4919,7 +4931,7 @@ void Bot::showDebugOverlay () {
|
|||
auto weapon = util.weaponIdToAlias (m_currentWeapon);
|
||||
|
||||
String debugData;
|
||||
debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", pev->netname.chars (), m_healthValue, pev->armorvalue, taskID, tasks[taskID], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality]);
|
||||
debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s Terrain=%s Stuck=%s\n", pev->netname.chars (), m_healthValue, pev->armorvalue, taskID, tasks[taskID], getTask ()->desire, weapon, getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy, pickup, personalities[m_personality], boolValue (m_checkTerrain), boolValue (m_isStuck));
|
||||
|
||||
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, overlayEntity)
|
||||
.writeByte (TE_TEXTMESSAGE)
|
||||
|
|
@ -4957,7 +4969,7 @@ void Bot::showDebugOverlay () {
|
|||
}
|
||||
|
||||
bool Bot::hasHostage () {
|
||||
if (cv_ignore_objectives.bool_ () || !game.mapIs (MapFlags::Demolition)) {
|
||||
if (cv_ignore_objectives.bool_ () || game.mapIs (MapFlags::Demolition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -4983,7 +4995,7 @@ int Bot::getAmmo (int id) {
|
|||
const auto &prop = conf.getWeaponProp (id);
|
||||
|
||||
if (prop.ammo1 == -1 || prop.ammo1 > kMaxWeapons - 1) {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
return m_ammo[prop.ammo1];
|
||||
}
|
||||
|
|
@ -5500,7 +5512,7 @@ void Bot::updateHearing () {
|
|||
// did the bot hear someone ?
|
||||
if (player != nullptr && util.isPlayer (player)) {
|
||||
// change to best weapon if heard something
|
||||
if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !cv_jasonmode.bool_ ()) {
|
||||
if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !isKnifeMode ()) {
|
||||
selectBestWeapon ();
|
||||
}
|
||||
|
||||
|
|
|
|||
207
src/combat.cpp
207
src/combat.cpp
|
|
@ -245,6 +245,7 @@ bool Bot::lookupEnemies () {
|
|||
newEnemy = player;
|
||||
}
|
||||
}
|
||||
auto distanceScale = usesKnife () ? 1.0f : 0.75f;
|
||||
|
||||
// the old enemy is no longer visible or
|
||||
if (game.isNullEntity (newEnemy)) {
|
||||
|
|
@ -271,7 +272,7 @@ bool Bot::lookupEnemies () {
|
|||
float scaleFactor = (1.0f / calculateScaleFactor (intresting));
|
||||
float distance = intresting->v.origin.distanceSq (pev->origin) * scaleFactor;
|
||||
|
||||
if (distance * 0.65f < nearestDistance) {
|
||||
if (distance * distanceScale < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
newEnemy = intresting;
|
||||
}
|
||||
|
|
@ -304,7 +305,7 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
float distance = player->v.origin.distanceSq (pev->origin);
|
||||
|
||||
if (distance * 0.65f < nearestDistance) {
|
||||
if (distance * distanceScale < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
newEnemy = player;
|
||||
|
||||
|
|
@ -315,7 +316,7 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
}
|
||||
}
|
||||
m_enemyUpdateTime = cr::clamp (game.time () + m_frameInterval * 25.0f, 0.5f, 0.75f);
|
||||
m_enemyUpdateTime = game.time () + cr::clamp (m_frameInterval * 3.0f, 0.2f, 0.64f);
|
||||
|
||||
if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) {
|
||||
newEnemy = shieldEnemy;
|
||||
|
|
@ -445,16 +446,20 @@ Vector Bot::getBodyOffsetError (float distance) {
|
|||
}
|
||||
|
||||
if (m_aimErrorTime < game.time ()) {
|
||||
const float error = distance / (cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 1000.0f);
|
||||
const float hitError = distance / (cr::clamp (m_difficulty, 1, 4) * 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));
|
||||
m_aimErrorTime = game.time () + rg.get (1.0f, 1.2f);
|
||||
m_aimLastError = Vector (rg.get (mins.x * hitError, maxs.x * hitError), rg.get (mins.y * hitError, maxs.y * hitError), rg.get (mins.z * hitError, maxs.z * hitError));
|
||||
|
||||
auto &aimError = conf.getDifficultyTweaks (m_difficulty) ->aimError;
|
||||
m_aimLastError += Vector (rg.get (-aimError.x, aimError.x), rg.get (-aimError.y, aimError.y), rg.get (-aimError.z, aimError.z));
|
||||
|
||||
m_aimErrorTime = game.time () + rg.get (1.5f, 2.0f);
|
||||
}
|
||||
return m_aimLastError;
|
||||
}
|
||||
|
||||
const Vector &Bot::getEnemyBodyOffset () {
|
||||
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.
|
||||
|
||||
|
|
@ -474,11 +479,15 @@ const Vector &Bot::getEnemyBodyOffset () {
|
|||
m_enemyParts &= ~Visibility::Head;
|
||||
}
|
||||
|
||||
const auto headOffset = [] (edict_t *e) {
|
||||
return e->v.absmin.z + e->v.size.z * 0.81f;
|
||||
};
|
||||
|
||||
Vector spot = m_enemy->v.origin;
|
||||
Vector compensation = 1.0f * m_frameInterval * m_enemy->v.velocity - 1.0f * m_frameInterval * pev->velocity;
|
||||
Vector compensation = nullptr;
|
||||
|
||||
if (!usesSniper () && distance > kDoubleSprayDistance) {
|
||||
compensation *= m_frameInterval;
|
||||
compensation = (m_enemy->v.velocity - pev->velocity) * m_frameInterval * 2.0f;
|
||||
compensation.z = 0.0f;
|
||||
}
|
||||
else {
|
||||
|
|
@ -490,71 +499,67 @@ const Vector &Bot::getEnemyBodyOffset () {
|
|||
spot += getBodyOffsetError (distance);
|
||||
}
|
||||
else if (util.isPlayer (m_enemy)) {
|
||||
const float highOffset = m_kpdRatio < 1.0f ? 1.5f : 0.0f;
|
||||
|
||||
// now take in account different parts of enemy body
|
||||
if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
|
||||
auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
|
||||
auto onLoosingStreak = (m_fearLevel > m_agressionLevel || m_kpdRatio < 1.25f);
|
||||
|
||||
// reduce headshot percent in case we're play too good
|
||||
if (!onLoosingStreak) {
|
||||
headshotPct = cr::abs (headshotPct - headshotPct / 4);
|
||||
}
|
||||
|
||||
// now check is our skill match to aim at head, else aim at enemy body
|
||||
if (rg.chance (headshotPct)) {
|
||||
if (onLoosingStreak) {
|
||||
spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance));
|
||||
spot = m_enemyOrigin + getCustomHeight (distance);
|
||||
}
|
||||
else {
|
||||
spot = m_enemyOrigin;
|
||||
}
|
||||
}
|
||||
else {
|
||||
spot.z += highOffset;
|
||||
spot = m_enemy->v.origin;
|
||||
spot.z -= 3.0f;
|
||||
}
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Body) {
|
||||
spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance));
|
||||
spot = m_enemy->v.origin;
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Other) {
|
||||
spot = m_enemyOrigin;
|
||||
}
|
||||
else if (m_enemyParts & Visibility::Head) {
|
||||
spot = m_enemyOrigin + Vector (0.0f, 0.0f, getEnemyBodyOffsetCorrection (distance));
|
||||
spot = m_enemyOrigin + getCustomHeight (distance);
|
||||
}
|
||||
}
|
||||
Vector newSpot = spot;
|
||||
|
||||
m_enemyOrigin = spot + compensation;
|
||||
m_lastEnemyOrigin = spot + compensation;
|
||||
if (m_difficulty < Difficulty::Expert && isEnemyInSight (newSpot)) {
|
||||
spot = newSpot + ((spot - newSpot) * 0.01f); // gradually adjust the aiming direction
|
||||
}
|
||||
spot += compensation;
|
||||
|
||||
if (usesKnife () && m_difficulty >= Difficulty::Normal) {
|
||||
spot.z = headOffset (m_enemy);
|
||||
}
|
||||
m_lastEnemyOrigin = spot;
|
||||
|
||||
// add some error to unskilled bots
|
||||
if (m_difficulty < Difficulty::Hard) {
|
||||
m_enemyOrigin += getBodyOffsetError (distance);
|
||||
spot += getBodyOffsetError (distance);
|
||||
}
|
||||
return m_enemyOrigin;
|
||||
return spot;
|
||||
}
|
||||
|
||||
float Bot::getEnemyBodyOffsetCorrection (float distance) {
|
||||
Vector Bot::getCustomHeight (float distance) {
|
||||
enum DistanceIndex {
|
||||
Long, Middle, Short
|
||||
};
|
||||
|
||||
static float offsetRanges[9][3] = {
|
||||
static constexpr float offsetRanges[9][3] = {
|
||||
{ 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
|
||||
{ 0.0f, 0.0f, 0.0f }, // melee
|
||||
{ 1.5f, -3.0f, -4.5f }, // pistol
|
||||
{ 6.5f, 6.0f, -2.0f }, // shotgun
|
||||
{ 2.5f -7.5f, -9.5f }, // zoomrifle
|
||||
{ 2.5f, -7.5f, -9.5f }, // rifle
|
||||
{ 2.5f, -7.5f, -9.5f }, // smg
|
||||
{ 0.0f, -2.5f, -6.0f }, // sniper
|
||||
{ 1.5f, -4.0f, -9.0f } // heavy
|
||||
};
|
||||
|
||||
// only highskilled bots do that
|
||||
if (m_difficulty != Difficulty::Expert) {
|
||||
if (m_difficulty != Difficulty::Expert || (m_enemy->v.flags & FL_DUCKING)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +573,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
|
|||
else if (distance > kSprayDistance && distance <= kDoubleSprayDistance) {
|
||||
distanceIndex = DistanceIndex::Middle;
|
||||
}
|
||||
return offsetRanges[m_weaponType][distanceIndex];
|
||||
return { 0.0f, 0.0f, offsetRanges[m_weaponType][distanceIndex] };
|
||||
}
|
||||
|
||||
bool Bot::isFriendInLineOfFire (float distance) {
|
||||
|
|
@ -742,7 +747,7 @@ bool Bot::isPenetrableObstacle3 (const Vector &dest) {
|
|||
bool Bot::needToPauseFiring (float distance) {
|
||||
// returns true if bot needs to pause between firing to compensate for punchangle & weapon spread
|
||||
|
||||
if (usesSniper ()) {
|
||||
if (usesSniper () || m_isUsingGrenade) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -761,22 +766,21 @@ bool Bot::needToPauseFiring (float distance) {
|
|||
return false;
|
||||
}
|
||||
else if (distance < kDoubleSprayDistance) {
|
||||
offset = 8.0f;
|
||||
offset = 2.75f;
|
||||
}
|
||||
const float xPunch = cr::deg2rad (pev->punchangle.x);
|
||||
const float yPunch = cr::deg2rad (pev->punchangle.y);
|
||||
const float xPunch = cr::square (cr::deg2rad (pev->punchangle.x));
|
||||
const float yPunch = cr::square (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 maxRecoil = static_cast <float> (conf.getDifficultyTweaks (m_difficulty)->maxRecoil);
|
||||
|
||||
// check if we need to compensate recoil
|
||||
if (cr::tanf (cr::sqrtf (cr::abs (xPunch * xPunch) + cr::abs (yPunch * yPunch))) * distance > offset + 30.0f + tolerance) {
|
||||
if (cr::tanf (cr::sqrtf (cr::abs (xPunch) + cr::abs (yPunch))) * distance > offset + maxRecoil + tolerance) {
|
||||
if (m_firePause < game.time ()) {
|
||||
m_firePause = rg.get (0.65f, 0.65f + 0.3f * tolerance);
|
||||
m_firePause = game.time () + rg.get (baseTime, baseTime + maxRecoil * 0.01f * tolerance) - interval;
|
||||
}
|
||||
m_firePause -= interval;
|
||||
m_firePause += game.time ();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -874,10 +878,10 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
}
|
||||
|
||||
// need to care for burst fire?
|
||||
if (distance < kSprayDistance || m_blindTime > game.time ()) {
|
||||
if (distance < kSprayDistance || m_blindTime > game.time () || usesKnife ()) {
|
||||
if (id == Weapon::Knife) {
|
||||
if (distance < 64.0f) {
|
||||
if (rg.chance (30) || hasShield ()) {
|
||||
if (distance < 72.0f) {
|
||||
if (rg.chance (40) || hasShield ()) {
|
||||
pev->button |= IN_ATTACK; // use primary attack
|
||||
}
|
||||
else {
|
||||
|
|
@ -901,16 +905,16 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
m_shootTime = game.time ();
|
||||
}
|
||||
else {
|
||||
if (needToPauseFiring (distance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't attack with knife over long distance
|
||||
if (id == Weapon::Knife) {
|
||||
m_shootTime = game.time ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needToPauseFiring (distance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab[choosen].primaryFireHold) {
|
||||
m_shootTime = game.time ();
|
||||
m_zoomCheckTime = game.time ();
|
||||
|
|
@ -922,8 +926,8 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
pev->button |= IN_ATTACK;
|
||||
}
|
||||
|
||||
const float minDelay[] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.6f };
|
||||
const float maxDelay[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f };
|
||||
constexpr float minDelay[] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.6f };
|
||||
constexpr float maxDelay[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f };
|
||||
|
||||
const int offset = cr::abs <int> (m_difficulty * 25 / 20 - 5);
|
||||
|
||||
|
|
@ -935,26 +939,29 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
|
||||
void Bot::fireWeapons () {
|
||||
// this function will return true if weapon was fired, false otherwise
|
||||
|
||||
//
|
||||
// do not handle this if with grenade, as it's done it throw grendate task
|
||||
if (m_isUsingGrenade) {
|
||||
return;
|
||||
}
|
||||
float distance = m_lookAt.distance (getEyesPos ()); // how far away is the enemy?
|
||||
|
||||
// or if friend in line of fire, stop this too but do not update shoot time
|
||||
if (!game.isNullEntity (m_enemy)) {
|
||||
if (isFriendInLineOfFire (distance)) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
m_lastFightStyleCheck = game.time ();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto tab = conf.getRawWeapons ();
|
||||
edict_t *enemy = m_enemy;
|
||||
auto enemy = m_enemy;
|
||||
|
||||
int selectId = Weapon::Knife, selectIndex = 0, choosenWeapon = 0;
|
||||
int weapons = pev->weapons;
|
||||
|
||||
// if jason mode use knife only
|
||||
if (cv_jasonmode.bool_ ()) {
|
||||
if (isKnifeMode ()) {
|
||||
selectWeapons (distance, selectIndex, selectId, choosenWeapon);
|
||||
return;
|
||||
}
|
||||
|
|
@ -997,7 +1004,7 @@ void Bot::fireWeapons () {
|
|||
m_reloadState = Reload::Primary;
|
||||
m_reloadCheckTime = game.time ();
|
||||
|
||||
if (rg.chance (cr::abs (m_difficulty * 25 - 100)) && rg.chance (15)) {
|
||||
if (rg.chance (cr::abs (m_difficulty * 25 - 100)) && rg.chance (20)) {
|
||||
pushRadioMessage (Radio::NeedBackup);
|
||||
}
|
||||
}
|
||||
|
|
@ -1015,6 +1022,8 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
|
|||
// this function checks, is it better to use pistol instead of current primary weapon
|
||||
// to attack our enemy, since current weapon is not very good in this situation.
|
||||
|
||||
// do not switch weapons when crossing the distance line
|
||||
if (m_lastBadWeaponSwitchTime + 3.0f < game.time ()) {
|
||||
auto &info = conf.getWeapons ();
|
||||
|
||||
if (m_difficulty < Difficulty::Normal || !hasSecondaryWeapon ()) {
|
||||
|
|
@ -1032,14 +1041,17 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
|
|||
}
|
||||
|
||||
// better use pistol in short range distances, when using sniper weapons
|
||||
if (weaponType == WeaponType::Sniper && distance < 450.0f) {
|
||||
if (weaponType == WeaponType::Sniper && distance < 400.0f) {
|
||||
m_lastBadWeaponSwitchTime = game.time ();
|
||||
return true;
|
||||
}
|
||||
|
||||
// shotguns is too inaccurate at long distances, so weapon is bad
|
||||
if (weaponType == WeaponType::Shotgun && distance > 750.0f) {
|
||||
m_lastBadWeaponSwitchTime = game.time ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1051,7 +1063,7 @@ void Bot::focusEnemy () {
|
|||
// aim for the head and/or body
|
||||
m_lookAt = getEnemyBodyOffset ();
|
||||
|
||||
if (m_enemySurpriseTime > game.time () && !usesKnife ()) {
|
||||
if (m_enemySurpriseTime > game.time ()) {
|
||||
return;
|
||||
}
|
||||
float distance = m_lookAt.distance2d (getEyesPos ()); // how far away is the enemy scum?
|
||||
|
|
@ -1061,9 +1073,6 @@ void Bot::focusEnemy () {
|
|||
if (distance < 80.0f) {
|
||||
m_wantsToFire = true;
|
||||
}
|
||||
else if (distance > 120.0f) {
|
||||
m_wantsToFire = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_wantsToFire = true;
|
||||
|
|
@ -1100,6 +1109,11 @@ void Bot::attackMovement () {
|
|||
return;
|
||||
}
|
||||
|
||||
// use enemy as dest origin if with knife
|
||||
if (usesKnife ()) {
|
||||
m_destOrigin = m_enemy->v.origin;
|
||||
}
|
||||
|
||||
if (m_lastUsedNodesTime - m_frameInterval > game.time ()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1136,10 +1150,6 @@ void Bot::attackMovement () {
|
|||
m_moveSpeed = pev->maxspeed;
|
||||
}
|
||||
|
||||
if (distance < 96.0f && !usesKnife ()) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
}
|
||||
|
||||
if (m_lastFightStyleCheck + 3.0f < game.time ()) {
|
||||
if (usesSniper ()) {
|
||||
m_fightStyle = Fight::Stay;
|
||||
|
|
@ -1167,7 +1177,7 @@ void Bot::attackMovement () {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75)) {
|
||||
else if (rg.get (0, 100) < (isInNarrowPlace () ? 25 : 75) || usesKnife ()) {
|
||||
m_fightStyle = Fight::Strafe;
|
||||
}
|
||||
else {
|
||||
|
|
@ -1181,6 +1191,13 @@ void Bot::attackMovement () {
|
|||
m_lastFightStyleCheck = game.time ();
|
||||
}
|
||||
|
||||
if (distance < 96.0f && !usesKnife ()) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
}
|
||||
else if (usesKnife ()) {
|
||||
m_fightStyle = Fight::None;
|
||||
}
|
||||
|
||||
if (m_fightStyle == Fight::Strafe) {
|
||||
auto swapStrafeCombatDir = [&] () {
|
||||
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);
|
||||
|
|
@ -1243,8 +1260,13 @@ void Bot::attackMovement () {
|
|||
pev->button |= IN_JUMP;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) {
|
||||
else if (m_fightStyle == Fight::Stay) {
|
||||
const bool alreadyDucking = m_duckTime > game.time () || isDucking ();
|
||||
|
||||
if (alreadyDucking) {
|
||||
m_duckTime = game.time () + m_frameInterval * 2.0f;
|
||||
}
|
||||
else if ((distance > kDoubleSprayDistance && hasPrimaryWeapon ()) && (m_enemyParts & (Visibility::Head | Visibility::Body)) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) {
|
||||
int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
|
||||
|
||||
if (graph.isVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) {
|
||||
|
|
@ -1255,10 +1277,6 @@ void Bot::attackMovement () {
|
|||
m_strafeSpeed = 0.0f;
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
|
@ -1465,7 +1483,11 @@ bool Bot::rateGroundWeapon (edict_t *ent) {
|
|||
}
|
||||
|
||||
bool Bot::hasAnyWeapons () {
|
||||
return (pev->weapons & (kPrimaryWeaponMask | kSecondaryWeaponMask));
|
||||
return !!(pev->weapons & (kPrimaryWeaponMask | kSecondaryWeaponMask));
|
||||
}
|
||||
|
||||
bool Bot::isKnifeMode () {
|
||||
return cv_jasonmode.bool_ () || (usesKnife () && !hasAnyWeapons ());
|
||||
}
|
||||
|
||||
void Bot::selectBestWeapon () {
|
||||
|
|
@ -1473,7 +1495,7 @@ void Bot::selectBestWeapon () {
|
|||
// current weapon to best one.
|
||||
|
||||
// if knife mode activated, force bot to use knife
|
||||
if (cv_jasonmode.bool_ ()) {
|
||||
if (isKnifeMode ()) {
|
||||
selectWeaponById (Weapon::Knife);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1649,7 +1671,7 @@ void Bot::checkReload () {
|
|||
bool uninterruptibleTask = (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::PickupItem || task == Task::ThrowExplosive || task == Task::ThrowFlashbang || task == Task::ThrowSmoke);
|
||||
|
||||
// do not check for reload
|
||||
if (uninterruptibleTask || m_isUsingGrenade) {
|
||||
if (uninterruptibleTask || m_isUsingGrenade || usesKnife ()) {
|
||||
m_reloadState = Reload::None;
|
||||
return;
|
||||
}
|
||||
|
|
@ -1849,7 +1871,7 @@ void Bot::checkGrenadesThrow () {
|
|||
};
|
||||
|
||||
// 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 ())) {
|
||||
if (preventibleTasks || isInNarrowPlace () || cv_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || (isKnifeMode () && !bots.isBombPlanted ()) || (m_grenadeRequested || m_grenadeCheckTime >= game.time ())) {
|
||||
clearThrowStates (m_states);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1915,7 +1937,7 @@ void Bot::checkGrenadesThrow () {
|
|||
if (radius < 164.0f) {
|
||||
radius = 164.0f;
|
||||
}
|
||||
auto predicted = graph.searchRadius (radius, pos, 12);
|
||||
auto predicted = graph.getNarestInRadius (radius, pos, 12);
|
||||
|
||||
if (predicted.empty ()) {
|
||||
m_states &= ~Sense::ThrowExplosive;
|
||||
|
|
@ -2029,3 +2051,14 @@ void Bot::checkGrenadesThrow () {
|
|||
clearThrowStates (m_states);
|
||||
}
|
||||
}
|
||||
|
||||
bool Bot::isEnemyInSight (Vector &endPos) {
|
||||
TraceResult aimHitTr {};
|
||||
game.testModel (getEyesPos (), getEyesPos () + pev->v_angle.forward () * kInfiniteDistance, 0, m_enemy, &aimHitTr);
|
||||
|
||||
if (aimHitTr.pHit != m_enemy) {
|
||||
return false;
|
||||
}
|
||||
endPos = aimHitTr.vecEndPos;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ BotConfig::BotConfig () {
|
|||
void BotConfig::loadConfigs () {
|
||||
setupMemoryFiles ();
|
||||
|
||||
loadCustomConfig ();
|
||||
loadNamesConfig ();
|
||||
loadChatConfig ();
|
||||
loadChatterConfig ();
|
||||
|
|
@ -28,7 +29,6 @@ void BotConfig::loadConfigs () {
|
|||
loadLogosConfig ();
|
||||
loadAvatarsConfig ();
|
||||
loadDifficultyConfig ();
|
||||
loadCustomConfig ();
|
||||
}
|
||||
|
||||
void BotConfig::loadMainConfig (bool isFirstLoad) {
|
||||
|
|
@ -533,27 +533,27 @@ void BotConfig::loadDifficultyConfig () {
|
|||
|
||||
// initialize defaults
|
||||
m_difficulty[Difficulty::Noob] = {
|
||||
{ 0.8f, 1.0f }, 5, 0, 0
|
||||
{ 0.8f, 1.0f }, 5, 0, 0, 38, { 30.0f, 30.0f, 40.0f }
|
||||
};
|
||||
|
||||
m_difficulty[Difficulty::Easy] = {
|
||||
{ 0.6f, 0.8f }, 30, 10, 10
|
||||
{ 0.6f, 0.8f }, 30, 10, 10, 32, { 15.0f, 15.0f, 24.0f }
|
||||
};
|
||||
|
||||
m_difficulty[Difficulty::Normal] = {
|
||||
{ 0.4f, 0.6f }, 50, 30, 40
|
||||
{ 0.4f, 0.6f }, 50, 30, 40, 26, { 5.0f, 5.0f, 10.0f }
|
||||
};
|
||||
|
||||
m_difficulty[Difficulty::Hard] = {
|
||||
{ 0.2f, 0.4f }, 75, 60, 70
|
||||
{ 0.2f, 0.4f }, 75, 60, 70, 23, { 0.0f, 0.0f, 0.0f }
|
||||
};
|
||||
|
||||
m_difficulty[Difficulty::Expert] = {
|
||||
{ 0.1f, 0.2f }, 100, 90, 90
|
||||
{ 0.1f, 0.2f }, 100, 90, 90, 21, { 0.0f, 0.0f, 0.0f }
|
||||
};
|
||||
|
||||
// currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob
|
||||
constexpr uint32_t kMaxDifficultyValues = 5;
|
||||
// currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob, recoil, aim_error {x,y,z}
|
||||
constexpr uint32_t kMaxDifficultyValues = 9;
|
||||
|
||||
// helper for parsing each level
|
||||
auto parseLevel = [&] (int32_t level, StringRef data) {
|
||||
|
|
@ -570,6 +570,10 @@ void BotConfig::loadDifficultyConfig () {
|
|||
diff->headshotPct = values[2].int_ ();
|
||||
diff->seenThruPct = values[3].int_ ();
|
||||
diff->hearThruPct = values[4].int_ ();
|
||||
diff->maxRecoil = values[5].int_ ();
|
||||
diff->aimError.x = values[6].float_ ();
|
||||
diff->aimError.y = values[7].float_ ();
|
||||
diff->aimError.z = values[8].float_ ();
|
||||
};
|
||||
|
||||
// avatars inititalization
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ int BotControl::cmdNodeClean () {
|
|||
if (strValue (option) == "all") {
|
||||
int removed = 0;
|
||||
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
for (auto i = 0; i < graph.length (); ++i) {
|
||||
removed += graph.clearConnections (i);
|
||||
}
|
||||
msg ("Done. Processed %d nodes. %d useless paths was cleared.", graph.length (), removed);
|
||||
|
|
@ -842,9 +842,9 @@ int BotControl::cmdNodeIterateCamp () {
|
|||
}
|
||||
}
|
||||
else if (op == "begin") {
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
if (graph[i].flags & NodeFlag::Camp) {
|
||||
m_campIterator.push (i);
|
||||
for (const auto &path : graph) {
|
||||
if (path.flags & NodeFlag::Camp) {
|
||||
m_campIterator.push (path.number);
|
||||
}
|
||||
}
|
||||
if (!m_campIterator.empty ()) {
|
||||
|
|
@ -877,8 +877,8 @@ int BotControl::cmdAdjustHeight () {
|
|||
auto heightOffset = floatValue (offset);
|
||||
|
||||
// adjust the height for all the nodes (negative values possible)
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
graph[i].origin.z += heightOffset;
|
||||
for (auto &path : graph) {
|
||||
path.origin.z += heightOffset;
|
||||
}
|
||||
return BotCommandResult::Handled;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,10 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w
|
|||
.writeByte (speed); // speed
|
||||
}
|
||||
|
||||
void Game::testModel (const Vector &start, const Vector &end, int hullNumber, edict_t *entToHit, TraceResult *ptr) {
|
||||
engfuncs.pfnTraceModel (start, end, hullNumber, entToHit, ptr);
|
||||
}
|
||||
|
||||
void Game::testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr) {
|
||||
// this function traces a line dot by dot, starting from vecStart in the direction of vecEnd,
|
||||
// ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ int BotGraph::getFarest (const Vector &origin, float maxDistance) {
|
|||
maxDistance = cr::square (maxDistance);
|
||||
|
||||
for (const auto &path : m_paths) {
|
||||
float distance = path.origin.distanceSq (origin);
|
||||
const float distance = path.origin.distanceSq (origin);
|
||||
|
||||
if (distance > maxDistance) {
|
||||
index = path.number;
|
||||
|
|
@ -472,7 +472,7 @@ int BotGraph::getNearestNoBuckets (const Vector &origin, float minDistance, int
|
|||
if (flags != -1 && !(path.flags & flags)) {
|
||||
continue; // if flag not -1 and node has no this flag, skip node
|
||||
}
|
||||
float distance = path.origin.distanceSq (origin);
|
||||
const float distance = path.origin.distanceSq (origin);
|
||||
|
||||
if (distance < minDistance) {
|
||||
index = path.number;
|
||||
|
|
@ -492,9 +492,12 @@ int BotGraph::getEditorNearest () {
|
|||
int BotGraph::getNearest (const Vector &origin, float minDistance, int flags) {
|
||||
// find the nearest node to that origin and return the index
|
||||
|
||||
auto &bucket = getNodesInBucket (origin);
|
||||
if (minDistance > 256.0f && !cr::fequal (minDistance, kInfiniteDistance)) {
|
||||
return getNearestNoBuckets (origin, minDistance, flags);
|
||||
}
|
||||
const auto &bucket = getNodesInBucket (origin);
|
||||
|
||||
if (bucket.empty ()) {
|
||||
if (bucket.length () < kMaxNodeLinks) {
|
||||
return getNearestNoBuckets (origin, minDistance, flags);
|
||||
}
|
||||
|
||||
|
|
@ -520,17 +523,26 @@ int BotGraph::getNearest (const Vector &origin, float minDistance, int flags) {
|
|||
return index;
|
||||
}
|
||||
|
||||
IntArray BotGraph::searchRadius (float radius, const Vector &origin, int maxCount) {
|
||||
IntArray BotGraph::getNarestInRadius (float radius, const Vector &origin, int maxCount) {
|
||||
// returns all nodes within radius from position
|
||||
|
||||
radius = cr::square (radius);
|
||||
|
||||
IntArray result;
|
||||
const auto &bucket = getNodesInBucket (origin);
|
||||
|
||||
if (bucket.empty ()) {
|
||||
result.push (getNearestNoBuckets (origin, radius));
|
||||
if (bucket.length () < kMaxNodeLinks || radius > cr::square (256.0f)) {
|
||||
for (const auto &path : m_paths) {
|
||||
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (origin.distanceSq (path.origin) < radius) {
|
||||
result.push (path.number);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
radius = cr::square (radius);
|
||||
|
||||
for (const auto &at : bucket) {
|
||||
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) {
|
||||
|
|
@ -3010,24 +3022,19 @@ BotGraph::BotGraph () {
|
|||
}
|
||||
|
||||
void BotGraph::initBuckets () {
|
||||
for (int x = 0; x < kMaxBucketsInsidePos; ++x) {
|
||||
for (int y = 0; y < kMaxBucketsInsidePos; ++y) {
|
||||
for (int z = 0; z < kMaxBucketsInsidePos; ++z) {
|
||||
m_buckets[x][y][z].reserve (kMaxNodesInsideBucket);
|
||||
m_buckets[x][y][z].clear ();
|
||||
}
|
||||
}
|
||||
}
|
||||
m_hashTable.clear ();
|
||||
}
|
||||
|
||||
void BotGraph::addToBucket (const Vector &pos, int index) {
|
||||
const auto &bucket = locateBucket (pos);
|
||||
m_buckets[bucket.x][bucket.y][bucket.z].push (index);
|
||||
m_hashTable[locateBucket (pos)].emplace (index);
|
||||
}
|
||||
|
||||
const Array <int32_t> &BotGraph::getNodesInBucket (const Vector &pos) {
|
||||
return m_hashTable[locateBucket (pos)];
|
||||
}
|
||||
|
||||
void BotGraph::eraseFromBucket (const Vector &pos, int index) {
|
||||
const auto &bucket = locateBucket (pos);
|
||||
auto &data = m_buckets[bucket.x][bucket.y][bucket.z];
|
||||
auto &data = m_hashTable[locateBucket (pos)];
|
||||
|
||||
for (size_t i = 0; i < data.length (); ++i) {
|
||||
if (data[i] == index) {
|
||||
|
|
@ -3037,19 +3044,13 @@ void BotGraph::eraseFromBucket (const Vector &pos, int index) {
|
|||
}
|
||||
}
|
||||
|
||||
BotGraph::Bucket BotGraph::locateBucket (const Vector &pos) {
|
||||
constexpr auto size = static_cast <float> (kMaxNodes * 2);
|
||||
int BotGraph::locateBucket (const Vector &pos) {
|
||||
constexpr auto width = cr::square (kMaxNodes);
|
||||
|
||||
return {
|
||||
cr::abs (static_cast <int> ((pos.x + size) / kMaxBucketSize)),
|
||||
cr::abs (static_cast <int> ((pos.y + size) / kMaxBucketSize)),
|
||||
cr::abs (static_cast <int> ((pos.z + size) / kMaxBucketSize))
|
||||
auto hash = [&] (float axis, int32_t shift) {
|
||||
return ((static_cast <int> (axis) + width) & 0x007f80) >> shift;
|
||||
};
|
||||
}
|
||||
|
||||
const SmallArray <int32_t> &BotGraph::getNodesInBucket (const Vector &pos) {
|
||||
const auto &bucket = locateBucket (pos);
|
||||
return m_buckets[bucket.x][bucket.y][bucket.z];
|
||||
return hash (pos.x, 15) + hash (pos.y, 7);
|
||||
}
|
||||
|
||||
void BotGraph::updateGlobalPractice () {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ ConVar cv_join_team ("yb_join_team", "any", "Forces all bots to join team specif
|
|||
ConVar cv_join_delay ("yb_join_delay", "5.0", "Specifies after how many seconds bots should start to join the game after the changelevel.", true, 0.0f, 30.0f);
|
||||
ConVar cv_name_prefix ("yb_name_prefix", "", "All the bot names will be prefixed with string specified with this cvar.", false);
|
||||
|
||||
ConVar cv_difficulty ("yb_difficulty", "4", "All bots difficulty level. Changing at runtime will affect already created bots.", true, 0.0f, 4.0f);
|
||||
ConVar cv_difficulty ("yb_difficulty", "3", "All bots difficulty level. Changing at runtime will affect already created bots.", true, 0.0f, 4.0f);
|
||||
|
||||
ConVar cv_difficulty_min ("yb_difficulty_min", "-1", "Lower bound of random difficulty on bot creation. Only affects newly created bots. -1 means yb_difficulty only used.", true, -1.0f, 4.0f);
|
||||
ConVar cv_difficulty_max ("yb_difficulty_max", "-1", "Upper bound of random difficulty on bot creation. Only affects newly created bots. -1 means yb_difficulty only used.", true, -1.0f, 4.0f);
|
||||
|
|
@ -1285,6 +1285,7 @@ void Bot::newRound () {
|
|||
m_isLeader = false;
|
||||
m_hasProgressBar = false;
|
||||
m_canChooseAimDirection = true;
|
||||
m_switchedToKnifeDuringJump = false;
|
||||
m_preventFlashing = 0.0f;
|
||||
|
||||
m_timeTeamOrder = 0.0f;
|
||||
|
|
@ -1410,6 +1411,7 @@ void Bot::newRound () {
|
|||
m_combatStrafeDir = Dodge::None;
|
||||
m_fightStyle = Fight::None;
|
||||
m_lastFightStyleCheck = 0.0f;
|
||||
m_lastBadWeaponSwitchTime = 0.0f;
|
||||
|
||||
m_checkWeaponSwitch = true;
|
||||
m_checkKnifeSwitch = true;
|
||||
|
|
|
|||
224
src/navigate.cpp
224
src/navigate.cpp
|
|
@ -103,7 +103,7 @@ int Bot::findBestGoal () {
|
|||
else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) {
|
||||
// send some terrorists to guard planted bomb
|
||||
if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0f) {
|
||||
return pushToHistroy (m_chosenGoalIndex = graph.getNearest (graph.getBombOrigin ()));
|
||||
return pushToHistroy (m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ()));
|
||||
}
|
||||
}
|
||||
else if (game.mapIs (MapFlags::Escape)) {
|
||||
|
|
@ -844,7 +844,7 @@ bool Bot::updateNavigation () {
|
|||
m_desiredVelocity = nullptr;
|
||||
}
|
||||
}
|
||||
else if (!cv_jasonmode.bool_ () && usesKnife () && isOnFloor ()) {
|
||||
else if (!isKnifeMode () && m_switchedToKnifeDuringJump) {
|
||||
selectBestWeapon ();
|
||||
|
||||
// if jump distance was big enough, cooldown a little
|
||||
|
|
@ -852,6 +852,7 @@ bool Bot::updateNavigation () {
|
|||
startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.45f, false);
|
||||
}
|
||||
m_jumpDistance = 0.0f;
|
||||
m_switchedToKnifeDuringJump = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1041,7 +1042,7 @@ bool Bot::updateNavigation () {
|
|||
desiredDistance = 0.0f;
|
||||
}
|
||||
else if (isOccupiedNode (m_path->number)) {
|
||||
desiredDistance = 72.0f;
|
||||
desiredDistance = 96.0f;
|
||||
}
|
||||
else {
|
||||
desiredDistance = m_path->radius;
|
||||
|
|
@ -1601,16 +1602,16 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
|
|||
};
|
||||
|
||||
// square distance heuristic with hostages
|
||||
auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float {
|
||||
if (graph[startIndex].flags & NodeFlag::NoHostage) {
|
||||
auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int, int goalIndex) -> float {
|
||||
if (graph[index].flags & NodeFlag::NoHostage) {
|
||||
return 65355.0f;
|
||||
}
|
||||
return hfunctionPathDist (index, startIndex, goalIndex);
|
||||
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex);
|
||||
};
|
||||
|
||||
// none heuristic
|
||||
auto hfunctionNone = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float {
|
||||
return hfunctionPathDist (index, startIndex, goalIndex) / (128.0f * 10.0f);
|
||||
auto hfunctionNone = [&hfunctionPathDist] (int index, int, int goalIndex) -> float {
|
||||
return hfunctionPathDist (index, kInvalidNodeIndex, goalIndex) / (128.0f * 10.0f);
|
||||
};
|
||||
|
||||
if (!graph.exists (srcIndex)) {
|
||||
|
|
@ -1673,7 +1674,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
|
|||
|
||||
// put start node into open list
|
||||
srcRoute->g = gcalc (m_team, srcIndex, kInvalidNodeIndex);
|
||||
srcRoute->f = srcRoute->g + hcalc (srcIndex, srcIndex, destIndex);
|
||||
srcRoute->f = srcRoute->g + hcalc (srcIndex, kInvalidNodeIndex, destIndex);
|
||||
srcRoute->state = RouteState::Open;
|
||||
|
||||
m_routeQue.clear ();
|
||||
|
|
@ -1724,7 +1725,7 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
|
|||
|
||||
// calculate the F value as F = G + H
|
||||
const float g = curRoute->g + gcalc (m_team, child.index, currentIndex);
|
||||
const float h = hcalc (child.index, srcIndex, destIndex);
|
||||
const float h = hcalc (child.index, kInvalidNodeIndex, destIndex);
|
||||
const float f = g + h;
|
||||
|
||||
if (childRoute->state == RouteState::New || childRoute->f > f) {
|
||||
|
|
@ -1747,14 +1748,13 @@ void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath:
|
|||
|
||||
void Bot::clearSearchNodes () {
|
||||
m_pathWalk.clear ();
|
||||
m_chosenGoalIndex = kInvalidNodeIndex;
|
||||
}
|
||||
|
||||
void Bot::clearRoute () {
|
||||
m_routes.resize (static_cast <size_t> (graph.length ()));
|
||||
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
auto route = &m_routes[i];
|
||||
for (const auto &path : graph) {
|
||||
auto route = &m_routes[path.number];
|
||||
|
||||
route->g = route->f = 0.0f;
|
||||
route->parent = kInvalidNodeIndex;
|
||||
|
|
@ -1801,28 +1801,25 @@ bool Bot::findNextBestNode () {
|
|||
// this function find a node in the near of the bot if bot had lost his path of pathfinder needs
|
||||
// to be restarted over again.
|
||||
|
||||
int busy = kInvalidNodeIndex;
|
||||
int busyIndex = kInvalidNodeIndex;
|
||||
|
||||
float lessDist[3] = { kInfiniteDistance, kInfiniteDistance, kInfiniteDistance };
|
||||
int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex , kInvalidNodeIndex };
|
||||
|
||||
auto &bucket = graph.getNodesInBucket (pev->origin);
|
||||
int numToSkip = cr::clamp (rg.get (0, 2), 0, static_cast <int> (bucket.length () / 2));
|
||||
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
|
||||
const auto &bucket = graph.getNodesInBucket (origin);
|
||||
|
||||
for (const int at : bucket) {
|
||||
bool skip = !!(at == m_currentNodeIndex);
|
||||
for (const auto &i : bucket) {
|
||||
const auto &path = graph[i];
|
||||
|
||||
// skip the current node, if any
|
||||
if (skip && numToSkip > 0) {
|
||||
if (!graph.exists (path.number)) {
|
||||
continue;
|
||||
}
|
||||
bool skip = !!(path.number == m_currentNodeIndex);
|
||||
|
||||
// skip current and recent previous nodes
|
||||
for (int j = 0; j < numToSkip; ++j) {
|
||||
if (at == m_previousNodes[j]) {
|
||||
// skip current or recent previous node
|
||||
if (path.number == m_previousNodes[0]) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// skip node from recent list
|
||||
|
|
@ -1831,28 +1828,28 @@ bool Bot::findNextBestNode () {
|
|||
}
|
||||
|
||||
// cts with hostages should not pick nodes with no hostage flag
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[at].flags & NodeFlag::NoHostage) && hasHostage ()) {
|
||||
if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[path.number].flags & NodeFlag::NoHostage) && hasHostage ()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check we're have link to it
|
||||
if (m_currentNodeIndex != kInvalidNodeIndex && !graph.isConnected (m_currentNodeIndex, at)) {
|
||||
if (m_currentNodeIndex != kInvalidNodeIndex && !graph.isConnected (m_currentNodeIndex, path.number)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore non-reacheable nodes...
|
||||
if (!isReachableNode (at)) {
|
||||
if (!isReachableNode (path.number)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if node is already used by another bot...
|
||||
if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (at)) {
|
||||
busy = at;
|
||||
if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (path.number)) {
|
||||
busyIndex = path.number;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we're still here, find some close nodes
|
||||
float distance = pev->origin.distanceSq (graph[at].origin);
|
||||
float distance = pev->origin.distanceSq (path.origin);
|
||||
|
||||
if (distance < lessDist[0]) {
|
||||
lessDist[2] = lessDist[1];
|
||||
|
|
@ -1862,18 +1859,18 @@ bool Bot::findNextBestNode () {
|
|||
lessIndex[1] = lessIndex[0];
|
||||
|
||||
lessDist[0] = distance;
|
||||
lessIndex[0] = at;
|
||||
lessIndex[0] = path.number;
|
||||
}
|
||||
else if (distance < lessDist[1]) {
|
||||
lessDist[2] = lessDist[1];
|
||||
lessIndex[2] = lessIndex[1];
|
||||
|
||||
lessDist[1] = distance;
|
||||
lessIndex[1] = at;
|
||||
lessIndex[1] = path.number;
|
||||
}
|
||||
else if (distance < lessDist[2]) {
|
||||
lessDist[2] = distance;
|
||||
lessIndex[2] = at;
|
||||
lessIndex[2] = path.number;
|
||||
}
|
||||
}
|
||||
int selected = kInvalidNodeIndex;
|
||||
|
|
@ -1894,8 +1891,8 @@ bool Bot::findNextBestNode () {
|
|||
selected = lessIndex[index];
|
||||
|
||||
// if we're still have no node and have busy one (by other bot) pick it up
|
||||
if (selected == kInvalidNodeIndex && busy != kInvalidNodeIndex) {
|
||||
selected = busy;
|
||||
if (selected == kInvalidNodeIndex && busyIndex != kInvalidNodeIndex) {
|
||||
selected = busyIndex;
|
||||
}
|
||||
|
||||
// worst case... find atleast something
|
||||
|
|
@ -1909,26 +1906,27 @@ bool Bot::findNextBestNode () {
|
|||
}
|
||||
|
||||
float Bot::getEstimatedNodeReachTime () {
|
||||
float estimatedTime = 2.8f;
|
||||
float estimatedTime = 6.0f;
|
||||
|
||||
// if just fired at enemy, increase reachability
|
||||
if (m_shootTime + 0.15f < game.time ()) {
|
||||
return estimatedTime;
|
||||
}
|
||||
|
||||
// calculate 'real' time that we need to get from one node to another
|
||||
if (graph.exists (m_currentNodeIndex) && graph.exists (m_previousNodes[0])) {
|
||||
float distance = graph[m_previousNodes[0]].origin.distance (graph[m_currentNodeIndex].origin);
|
||||
float distance = graph[m_previousNodes[0]].origin.distanceSq (graph[m_currentNodeIndex].origin);
|
||||
|
||||
// caclulate estimated time
|
||||
if (pev->maxspeed <= 0.0f) {
|
||||
estimatedTime = 3.0f * distance / 240.0f;
|
||||
}
|
||||
else {
|
||||
estimatedTime = 3.0f * distance / pev->maxspeed;
|
||||
}
|
||||
estimatedTime = 5.0f * (distance / cr::square (m_moveSpeed + 1.0f));
|
||||
|
||||
bool longTermReachability = (m_pathFlags & NodeFlag::Crouch) || (m_pathFlags & NodeFlag::Ladder) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK);
|
||||
|
||||
// check for special nodes, that can slowdown our movement
|
||||
if (longTermReachability) {
|
||||
estimatedTime *= 2.0f;
|
||||
}
|
||||
estimatedTime = cr::clamp (estimatedTime, 2.0f, longTermReachability ? 8.0f : 5.0f);
|
||||
estimatedTime = cr::clamp (estimatedTime, 3.0f, longTermReachability ? 8.0f : 6.0f);
|
||||
}
|
||||
return estimatedTime + m_frameInterval;
|
||||
}
|
||||
|
|
@ -1941,8 +1939,7 @@ void Bot::findValidNode () {
|
|||
clearSearchNodes ();
|
||||
findNextBestNode ();
|
||||
}
|
||||
else if (m_navTimeset + getEstimatedNodeReachTime () < game.time () && game.isNullEntity (m_enemy)) {
|
||||
|
||||
else if (m_navTimeset + getEstimatedNodeReachTime () < game.time ()) {
|
||||
// increase danager for both teams
|
||||
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
|
||||
int damageValue = graph.getDangerDamage (team, m_currentNodeIndex, m_currentNodeIndex);
|
||||
|
|
@ -1989,42 +1986,31 @@ int Bot::findNearestNode () {
|
|||
// get the current nearest node to bot with visibility checks
|
||||
|
||||
int index = kInvalidNodeIndex;
|
||||
float minimum = cr::square (1024.0f);
|
||||
float minimumDistance = cr::square (1024.0f);
|
||||
|
||||
auto &bucket = graph.getNodesInBucket (pev->origin);
|
||||
const auto &origin = pev->origin + pev->velocity * m_frameInterval;
|
||||
const auto &bucket = graph.getNodesInBucket (origin);
|
||||
|
||||
for (const auto at : bucket) {
|
||||
if (at == m_currentNodeIndex) {
|
||||
for (const auto &i : bucket) {
|
||||
const auto &path = graph[i];
|
||||
|
||||
if (!graph.exists (path.number) || !graph.exists (m_currentNodeIndex) || path.number == m_currentNodeIndex) {
|
||||
continue;
|
||||
}
|
||||
float distance = graph[at].origin.distanceSq (pev->origin);
|
||||
const float distance = path.origin.distanceSq (pev->origin);
|
||||
|
||||
if (distance < minimum) {
|
||||
|
||||
// if bot doing navigation, make sure node really visible and not too high
|
||||
if ((m_currentNodeIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, at) && graph.isVisible (at, m_currentNodeIndex)) || isReachableNode (at)) {
|
||||
index = at;
|
||||
minimum = distance;
|
||||
if (distance < minimumDistance) {
|
||||
// if bot doing navigation, make sure node really visible and reacheable
|
||||
if (graph.isVisible (m_currentNodeIndex, path.number) && isReachableNode (path.number)) {
|
||||
index = path.number;
|
||||
minimumDistance = distance;
|
||||
}
|
||||
|
||||
// @pr-419: temporarily disabled due to cpu usage
|
||||
#if 0
|
||||
else {
|
||||
TraceResult tr {};
|
||||
game.testHull (getEyesPos (), graph[at].origin, TraceIgnore::Monsters, head_hull, ent (), &tr);
|
||||
|
||||
if (tr.flFraction >= 1.0f && !tr.fStartSolid) {
|
||||
index = at;
|
||||
minimum = distance;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// worst case, take any node...
|
||||
if (index == kInvalidNodeIndex) {
|
||||
index = graph.getNearestNoBuckets (pev->origin);
|
||||
index = graph.getNearestNoBuckets (origin);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
|
@ -2109,14 +2095,14 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
}
|
||||
|
||||
// find the best node now
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
for (const auto &path : graph) {
|
||||
// exclude ladder & current nodes
|
||||
if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || !graph.isVisible (i, posIndex)) {
|
||||
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || !graph.isVisible (path.number, posIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// use the 'real' pathfinding distances
|
||||
int distance = graph.getPathDist (srcIndex, i);
|
||||
int distance = graph.getPathDist (srcIndex, path.number);
|
||||
|
||||
// skip wayponts too far
|
||||
if (distance > kMaxDistance) {
|
||||
|
|
@ -2124,10 +2110,10 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
}
|
||||
|
||||
// skip occupied points
|
||||
if (isOccupiedNode (i)) {
|
||||
if (isOccupiedNode (path.number)) {
|
||||
continue;
|
||||
}
|
||||
game.testLine (graph[i].origin, graph[posIndex].origin, TraceIgnore::Glass, ent (), &tr);
|
||||
game.testLine (path.origin, graph[posIndex].origin, TraceIgnore::Glass, ent (), &tr);
|
||||
|
||||
// check if line not hit anything
|
||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||
|
|
@ -2135,35 +2121,35 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
}
|
||||
|
||||
if (distance > minDistance[0]) {
|
||||
nodeIndex[0] = i;
|
||||
nodeIndex[0] = path.number;
|
||||
minDistance[0] = distance;
|
||||
}
|
||||
else if (distance > minDistance[1]) {
|
||||
nodeIndex[1] = i;
|
||||
nodeIndex[1] = path.number;
|
||||
minDistance[1] = distance;
|
||||
}
|
||||
else if (distance > minDistance[2]) {
|
||||
nodeIndex[2] = i;
|
||||
nodeIndex[2] = path.number;
|
||||
minDistance[2] = distance;
|
||||
}
|
||||
else if (distance > minDistance[3]) {
|
||||
nodeIndex[3] = i;
|
||||
nodeIndex[3] = path.number;
|
||||
minDistance[3] = distance;
|
||||
}
|
||||
else if (distance > minDistance[4]) {
|
||||
nodeIndex[4] = i;
|
||||
nodeIndex[4] = path.number;
|
||||
minDistance[4] = distance;
|
||||
}
|
||||
else if (distance > minDistance[5]) {
|
||||
nodeIndex[5] = i;
|
||||
nodeIndex[5] = path.number;
|
||||
minDistance[5] = distance;
|
||||
}
|
||||
else if (distance > minDistance[6]) {
|
||||
nodeIndex[6] = i;
|
||||
nodeIndex[6] = path.number;
|
||||
minDistance[6] = distance;
|
||||
}
|
||||
else if (distance > minDistance[7]) {
|
||||
nodeIndex[7] = i;
|
||||
nodeIndex[7] = path.number;
|
||||
minDistance[7] = distance;
|
||||
}
|
||||
}
|
||||
|
|
@ -2198,9 +2184,9 @@ int Bot::findDefendNode (const Vector &origin) {
|
|||
if (nodeIndex[0] == kInvalidNodeIndex) {
|
||||
IntArray found;
|
||||
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
if (origin.distanceSq (graph[i].origin) < cr::square (static_cast <float> (kMaxDistance)) && !graph.isVisible (i, posIndex) && !isOccupiedNode (i)) {
|
||||
found.push (i);
|
||||
for (const auto &path : graph) {
|
||||
if (origin.distanceSq (path.origin) < cr::square (static_cast <float> (kMaxDistance)) && graph.isVisible (path.number, posIndex) && !isOccupiedNode (path.number)) {
|
||||
found.push (path.number);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2261,15 +2247,15 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
changeNodeIndex (srcIndex);
|
||||
|
||||
// find the best node now
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
for (const auto &path : graph) {
|
||||
// exclude ladder, current node and nodes seen by the enemy
|
||||
if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || graph.isVisible (enemyIndex, i)) {
|
||||
if ((path.flags & NodeFlag::Ladder) || path.number == srcIndex || graph.isVisible (enemyIndex, path.number)) {
|
||||
continue;
|
||||
}
|
||||
bool neighbourVisible = false; // now check neighbour nodes for visibility
|
||||
|
||||
for (auto &enemy : enemies) {
|
||||
if (graph.isVisible (enemy, i)) {
|
||||
if (graph.isVisible (enemy, path.number)) {
|
||||
neighbourVisible = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2281,43 +2267,43 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
}
|
||||
|
||||
// use the 'real' pathfinding distances
|
||||
int distance = graph.getPathDist (srcIndex, i);
|
||||
int enemyDistance = graph.getPathDist (enemyIndex, i);
|
||||
int distance = graph.getPathDist (srcIndex, path.number);
|
||||
int enemyDistance = graph.getPathDist (enemyIndex, path.number);
|
||||
|
||||
if (distance >= enemyDistance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (distance < minDistance[0]) {
|
||||
nodeIndex[0] = i;
|
||||
nodeIndex[0] = path.number;
|
||||
minDistance[0] = distance;
|
||||
}
|
||||
else if (distance < minDistance[1]) {
|
||||
nodeIndex[1] = i;
|
||||
nodeIndex[1] = path.number;
|
||||
minDistance[1] = distance;
|
||||
}
|
||||
else if (distance < minDistance[2]) {
|
||||
nodeIndex[2] = i;
|
||||
nodeIndex[2] = path.number;
|
||||
minDistance[2] = distance;
|
||||
}
|
||||
else if (distance < minDistance[3]) {
|
||||
nodeIndex[3] = i;
|
||||
nodeIndex[3] = path.number;
|
||||
minDistance[3] = distance;
|
||||
}
|
||||
else if (distance < minDistance[4]) {
|
||||
nodeIndex[4] = i;
|
||||
nodeIndex[4] = path.number;
|
||||
minDistance[4] = distance;
|
||||
}
|
||||
else if (distance < minDistance[5]) {
|
||||
nodeIndex[5] = i;
|
||||
nodeIndex[5] = path.number;
|
||||
minDistance[5] = distance;
|
||||
}
|
||||
else if (distance < minDistance[6]) {
|
||||
nodeIndex[6] = i;
|
||||
nodeIndex[6] = path.number;
|
||||
minDistance[6] = distance;
|
||||
}
|
||||
else if (distance < minDistance[7]) {
|
||||
nodeIndex[7] = i;
|
||||
nodeIndex[7] = path.number;
|
||||
minDistance[7] = distance;
|
||||
}
|
||||
}
|
||||
|
|
@ -2326,7 +2312,7 @@ int Bot::findCoverNode (float maxDistance) {
|
|||
for (int i = 0; i < kMaxNodeLinks; ++i) {
|
||||
if (nodeIndex[i] != kInvalidNodeIndex) {
|
||||
int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]);
|
||||
practice = (practice * 100) / kMaxPracticeDamageValue;
|
||||
practice = (practice * 100) / graph.getHighestDamageForTeam (m_team);
|
||||
|
||||
minDistance[i] = (practice * 100) / 8192;
|
||||
minDistance[i] += practice;
|
||||
|
|
@ -2380,15 +2366,16 @@ bool Bot::selectBestNextNode () {
|
|||
}
|
||||
|
||||
for (auto &link : m_path->links) {
|
||||
if (link.index != kInvalidNodeIndex && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) {
|
||||
if (graph.exists (link.index) && m_pathWalk.first () != link.index && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) {
|
||||
|
||||
// don't use ladder nodes as alternative
|
||||
if (graph[link.index].flags & NodeFlag::Ladder) {
|
||||
if (graph[link.index].flags & (NodeFlag::Ladder | PathFlag::Jump)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (graph[link.index].origin.z <= graph[m_currentNodeIndex].origin.z + 10.0f && !isOccupiedNode (link.index)) {
|
||||
m_pathWalk.first () = link.index;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -2520,6 +2507,9 @@ bool Bot::advanceMovement () {
|
|||
// is there a jump node right ahead and do we need to draw out the light weapon ?
|
||||
if (willJump && !usesKnife () && m_currentWeapon != Weapon::Scout && !m_isReloading && !usesPistol () && (m_jumpDistance > 175.0f || (dst.z - 32.0f > src.z && m_jumpDistance > 135.0f)) && !(m_states & Sense::SeeingEnemy)) {
|
||||
selectWeaponById (Weapon::Knife); // draw out the knife if we needed
|
||||
|
||||
// mark as switched
|
||||
m_switchedToKnifeDuringJump = true;
|
||||
}
|
||||
|
||||
// bot not already on ladder but will be soon?
|
||||
|
|
@ -3100,16 +3090,13 @@ int Bot::getRandomCampDir () {
|
|||
float distTab[kMaxNodesToSearch] {};
|
||||
uint16_t visibility[kMaxNodesToSearch] {};
|
||||
|
||||
int currentNode = m_currentNodeIndex;
|
||||
|
||||
for (int i = 0; i < graph.length (); ++i) {
|
||||
if (currentNode == i || !graph.isVisible (currentNode, i)) {
|
||||
for (const auto &path : graph) {
|
||||
if (m_currentNodeIndex == path.number || !graph.isVisible (m_currentNodeIndex, path.number)) {
|
||||
continue;
|
||||
}
|
||||
const auto &path = graph[i];
|
||||
|
||||
if (count < kMaxNodesToSearch) {
|
||||
indices[count] = i;
|
||||
indices[count] = path.number;
|
||||
|
||||
distTab[count] = pev->origin.distanceSq (path.origin);
|
||||
visibility[count] = path.vis.crouch + path.vis.stand;
|
||||
|
|
@ -3122,7 +3109,7 @@ int Bot::getRandomCampDir () {
|
|||
|
||||
for (int j = 0; j < kMaxNodesToSearch; ++j) {
|
||||
if (visBits >= visibility[j] && distance > distTab[j]) {
|
||||
indices[j] = i;
|
||||
indices[j] = path.number;
|
||||
|
||||
distTab[j] = distance;
|
||||
visibility[j] = visBits;
|
||||
|
|
@ -3179,12 +3166,12 @@ void Bot::updateLookAngles () {
|
|||
accelerate += 600.0f;
|
||||
}
|
||||
stiffness += 100.0f;
|
||||
damping += 5.0f;
|
||||
damping -= 5.0f;
|
||||
}
|
||||
m_idealAngles = pev->v_angle;
|
||||
|
||||
float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x);
|
||||
float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
|
||||
const float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x);
|
||||
const float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
|
||||
|
||||
if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) {
|
||||
m_lookYawVel = 0.0f;
|
||||
|
|
@ -3332,11 +3319,6 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// just in case, if something happend, and we're not updated yet
|
||||
if (!util.isAlive (client.ent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not check clients far away from us
|
||||
if (pev->origin.distanceSq (client.origin) > cr::square (320.0f)) {
|
||||
continue;
|
||||
|
|
@ -3347,7 +3329,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
|
|||
}
|
||||
auto length = client.origin.distanceSq (graph[index].origin);
|
||||
|
||||
if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (80.0f), cr::square (140.0f))) {
|
||||
if (length < cr::clamp (cr::square (graph[index].radius) * 2.0f, cr::square (40.0f), cr::square (90.0f))) {
|
||||
return true;
|
||||
}
|
||||
auto bot = bots[client.ent];
|
||||
|
|
@ -3355,7 +3337,7 @@ bool Bot::isOccupiedNode (int index, bool needZeroVelocity) {
|
|||
if (bot == nullptr || bot == this || !bot->m_notKilled) {
|
||||
continue;
|
||||
}
|
||||
return index == bot->m_currentNodeIndex || bot->getTask ()->data == index;
|
||||
return bot->m_currentNodeIndex == index;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue