vistable: fix long-standing bug with saving vis counts
vistable: bumped version to 4, so all vistables will be rebuilded bot: reworked bot think mechanism timers fix: gimbal lock within bot aiming code on ladders fix: some fixes to aiming code that prevent bots 360 degree rotations fix: some mistakes in next and next-next node aiming when in idle state fix: improved seek covering from attack task nav: improved bot's crouch on marred-crouch nodes nav: overall improvements to ladder handling code Co-Authored-By: Max <161382234+dyspose@users.noreply.github.com>
This commit is contained in:
parent
0de53173f0
commit
38551eae21
11 changed files with 151 additions and 98 deletions
|
|
@ -430,7 +430,7 @@ constexpr auto kGrenadeCheckTime = 0.6f;
|
|||
constexpr auto kSprayDistance = 360.0f;
|
||||
constexpr auto kSprayDistanceX2 = kSprayDistance * 2;
|
||||
constexpr auto kMaxChatterRepeatInterval = 99.0f;
|
||||
constexpr auto kViewFrameUpdate = 1.0f / 30.0f;
|
||||
constexpr auto kViewFrameUpdate = 1.0f / 25.0f;
|
||||
constexpr auto kGrenadeDamageRadius = 385.0f;
|
||||
|
||||
constexpr auto kInfiniteDistanceLong = static_cast <int> (kInfiniteDistance);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ CR_DECLARE_SCOPED_ENUM (StorageOption,
|
|||
CR_DECLARE_SCOPED_ENUM (StorageVersion,
|
||||
Graph = 2,
|
||||
Practice = 2,
|
||||
Vistable = 3,
|
||||
Vistable = 4,
|
||||
Matrix = 2,
|
||||
Podbot = 7
|
||||
)
|
||||
|
|
|
|||
16
inc/yapb.h
16
inc/yapb.h
|
|
@ -109,6 +109,12 @@ struct Client {
|
|||
ClientNoise noise;
|
||||
};
|
||||
|
||||
// think delay mapping
|
||||
struct FrameDelay {
|
||||
float interval {};
|
||||
float time {};
|
||||
};
|
||||
|
||||
// include bot graph stuff
|
||||
#include <graph.h>
|
||||
#include <vision.h>
|
||||
|
|
@ -235,7 +241,7 @@ private:
|
|||
float m_knifeAttackTime {}; // time to rush with knife (at the beginning of the round)
|
||||
float m_duckDefuseCheckTime {}; // time to check for ducking for defuse
|
||||
float m_frameInterval {}; // bot's frame interval
|
||||
float m_lastCommandTime {}; // time bot last thinked
|
||||
float m_previousThinkTime {}; // time bot last thinked
|
||||
float m_reloadCheckTime {}; // time to check reloading
|
||||
float m_zoomCheckTime {}; // time to check zoom again
|
||||
float m_shieldCheckTime {}; // time to check shield drawing again
|
||||
|
|
@ -345,7 +351,9 @@ private:
|
|||
Path *m_path {}; // pointer to the current path node
|
||||
String m_chatBuffer {}; // space for strings (say text...)
|
||||
Frustum::Planes m_viewFrustum {};
|
||||
|
||||
CountdownTimer m_forgetLastVictimTimer {}; // time to forget last victim position ?
|
||||
CountdownTimer m_approachingLadderTimer {}; // bot is approaching ladder
|
||||
|
||||
private:
|
||||
int pickBestWeapon (Array <int> &vec, int moneySave);
|
||||
|
|
@ -589,8 +597,6 @@ public:
|
|||
float m_agressionLevel {}; // dynamic aggression level (in game)
|
||||
float m_fearLevel {}; // dynamic fear level (in game)
|
||||
float m_nextEmotionUpdate {}; // next time to sanitize emotions
|
||||
float m_updateTime {}; // skip some frames in bot thinking
|
||||
float m_updateInterval {}; // interval between frames
|
||||
float m_goalValue {}; // ranking value for this node
|
||||
float m_viewDistance {}; // current view distance
|
||||
float m_maxViewDistance {}; // maximum view distance
|
||||
|
|
@ -675,9 +681,13 @@ public:
|
|||
BurstMode m_weaponBurstMode {}; // bot using burst mode? (famas/glock18, but also silencer mode)
|
||||
Personality m_personality {}; // bots type
|
||||
Array <BotTask> m_tasks {};
|
||||
|
||||
Deque <int32_t> m_msgQueue {};
|
||||
Array <int32_t> m_goalHist {};
|
||||
|
||||
FrameDelay m_thinkDelay {};
|
||||
FrameDelay m_commandDelay {};
|
||||
|
||||
public:
|
||||
Bot (edict_t *bot, int difficulty, int personality, int team, int skin);
|
||||
|
||||
|
|
|
|||
|
|
@ -1789,11 +1789,7 @@ void Bot::refreshEnemyPredict () {
|
|||
&& m_shootTime + 1.5f > game.time ();
|
||||
|
||||
if (!(m_aimFlags & (AimFlags::Enemy | AimFlags::PredictPath)) && !denyLastEnemy && seesEntity (m_lastEnemyOrigin, true)) {
|
||||
const auto weaponPenetratePower = conf.findWeaponById (m_currentWeapon).penetratePower;
|
||||
|
||||
if (isPenetrableObstacle3 (m_lastEnemyOrigin, weaponPenetratePower)) {
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1804,7 +1800,7 @@ void Bot::refreshEnemyPredict () {
|
|||
|
||||
void Bot::setLastVictim (edict_t *ent) {
|
||||
m_lastVictim = ent;
|
||||
m_lastVictimOrigin = ent->v.origin + ent->v.view_ofs;
|
||||
m_lastVictimOrigin = ent->v.origin;
|
||||
|
||||
m_forgetLastVictimTimer.start (rg.get (1.0f, 2.0f));
|
||||
}
|
||||
|
|
@ -2000,7 +1996,7 @@ void Bot::filterTasks () {
|
|||
float timeHeard = m_heardSoundTime - game.time ();
|
||||
float ratio = 0.0f;
|
||||
|
||||
m_retreatTime = game.time () + rg.get (3.0f, 15.0f);
|
||||
m_retreatTime = game.time () + rg.get (1.0f, 4.0f);
|
||||
|
||||
if (timeSeen > timeHeard) {
|
||||
timeSeen += 10.0f;
|
||||
|
|
@ -2836,11 +2832,18 @@ void Bot::checkParachute () {
|
|||
void Bot::frame () {
|
||||
pev->flags |= FL_CLIENT | FL_FAKECLIENT; // restore fake client bit, just in case
|
||||
|
||||
if (m_updateTime <= game.time ()) {
|
||||
if (m_thinkDelay.time <= game.time ()) {
|
||||
update ();
|
||||
}
|
||||
else if (m_isAlive) {
|
||||
updateLookAngles ();
|
||||
|
||||
|
||||
// delay next execution for thinking
|
||||
m_thinkDelay.time = game.time () + m_thinkDelay.interval;
|
||||
|
||||
// run bot command on twice speed
|
||||
if (m_commandDelay.time <= game.time ()) {
|
||||
runMovement ();
|
||||
m_commandDelay.time = game.time () + m_commandDelay.interval;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_slowFrameTimestamp > game.time ()) {
|
||||
|
|
@ -2955,10 +2958,6 @@ void Bot::update () {
|
|||
else if (!botMovement) {
|
||||
resetMovement ();
|
||||
}
|
||||
runMovement ();
|
||||
|
||||
// delay next execution
|
||||
m_updateTime = game.time () + m_updateInterval;
|
||||
}
|
||||
|
||||
void Bot::logicDuringFreezetime () {
|
||||
|
|
@ -2966,6 +2965,12 @@ void Bot::logicDuringFreezetime () {
|
|||
return;
|
||||
}
|
||||
|
||||
// simply skip randomly
|
||||
if (rg.chance (10)) {
|
||||
return;
|
||||
}
|
||||
pev->button &= ~IN_DUCK;
|
||||
|
||||
if (rg.chance (15) && m_jumpTime + rg.get (1.0, 2.0f) < game.time ()) {
|
||||
pev->button |= IN_JUMP;
|
||||
m_jumpTime = game.time ();
|
||||
|
|
@ -3215,9 +3220,7 @@ void Bot::logic () {
|
|||
m_moveSpeed = -pev->maxspeed;
|
||||
m_strafeSpeed = pev->maxspeed * static_cast <float> (m_needAvoidGrenade);
|
||||
}
|
||||
|
||||
ensureEntitiesClear ();
|
||||
translateInput ();
|
||||
|
||||
// check if need to use parachute
|
||||
checkParachute ();
|
||||
|
|
@ -3742,13 +3745,13 @@ bool Bot::canRunHeavyWeight () {
|
|||
uint8_t Bot::computeMsec () {
|
||||
// estimate msec to use for this command based on time passed from the previous command
|
||||
|
||||
return static_cast <uint8_t> (cr::min (static_cast <int32_t> (cr::roundf ((game.time () - m_lastCommandTime) * 1000.0f)), 255));
|
||||
return static_cast <uint8_t> (cr::min (static_cast <int32_t> (cr::roundf ((game.time () - m_previousThinkTime) * 1000.0f)), 255));
|
||||
}
|
||||
|
||||
const Vector &Bot::getRpmAngles () {
|
||||
// get angles to pass to run player move function
|
||||
|
||||
if ((m_pathFlags & NodeFlag::Ladder) || getCurrentTaskId () == Task::Attack) {
|
||||
if (!m_approachingLadderTimer.elapsed () || getCurrentTaskId () == Task::Attack) {
|
||||
return pev->v_angle;
|
||||
}
|
||||
return m_moveAngles;
|
||||
|
|
@ -3769,10 +3772,13 @@ void Bot::runMovement () {
|
|||
// elapses, that bot will behave like a ghost : no movement, but bullets and players can
|
||||
// pass through it. Then, when the next frame will begin, the stucking problem will arise !
|
||||
|
||||
m_frameInterval = game.time () - m_lastCommandTime;
|
||||
m_frameInterval = game.time () - m_previousThinkTime;
|
||||
|
||||
const auto msecVal = computeMsec ();
|
||||
m_lastCommandTime = game.time ();
|
||||
m_previousThinkTime = game.time ();
|
||||
|
||||
// translate bot buttons
|
||||
translateInput ();
|
||||
|
||||
engfuncs.pfnRunPlayerMove (pev->pContainingEntity,
|
||||
getRpmAngles (), m_moveSpeed, m_strafeSpeed,
|
||||
|
|
@ -3946,7 +3952,6 @@ void Bot::updateHearing () {
|
|||
// check if heard enemy can be shoot through some obstacle
|
||||
else {
|
||||
if (cv_shoots_thru_walls.bool_ ()
|
||||
&& m_difficulty >= Difficulty::Normal
|
||||
&& m_lastEnemy == hearedEnemy
|
||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->hearThruPct)
|
||||
&& m_seeEnemyTime + 3.0f > game.time ()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include <yapb.h>
|
||||
|
||||
ConVar cv_shoots_thru_walls ("shoots_thru_walls", "1", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f);
|
||||
ConVar cv_shoots_thru_walls ("shoots_thru_walls", "2", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f);
|
||||
ConVar cv_ignore_enemies ("ignore_enemies", "0", "Enables or disables searching world for enemies.");
|
||||
ConVar cv_check_enemy_rendering ("check_enemy_rendering", "0", "Enables or disables checking enemy rendering flags. Useful for some mods.");
|
||||
ConVar cv_check_enemy_invincibility ("check_enemy_invincibility", "0", "Enables or disables checking enemy invincibility. Useful for some mods.");
|
||||
|
|
@ -236,11 +236,6 @@ bool Bot::seesEnemy (edict_t *player) {
|
|||
&& frustum.check (m_viewFrustum, player)
|
||||
&& !isBehindSmokeClouds (player->v.origin)
|
||||
&& checkBodyParts (player)) {
|
||||
|
||||
m_seeEnemyTime = game.time ();
|
||||
m_lastEnemy = player;
|
||||
m_lastEnemyOrigin = m_enemyOrigin;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -274,7 +269,13 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f > game.time () && util.isAlive (m_lastEnemy)) {
|
||||
m_states |= Sense::SuspectEnemy;
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
|
||||
// check if last enemy can be penetrated
|
||||
const auto penetratePower = conf.findWeaponById (m_currentWeapon).penetratePower * 4;
|
||||
|
||||
if (isPenetrableObstacle3 (m_lastEnemyOrigin, penetratePower)) {
|
||||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
m_enemyParts = Visibility::None;
|
||||
m_enemyOrigin = nullptr;
|
||||
|
|
@ -475,7 +476,6 @@ bool Bot::lookupEnemies () {
|
|||
|
||||
// if no enemy visible check if last one shoot able through wall
|
||||
if (cv_shoots_thru_walls.bool_ ()
|
||||
&& m_difficulty >= Difficulty::Normal
|
||||
&& rg.chance (conf.getDifficultyTweaks (m_difficulty)->seenThruPct)
|
||||
&& isPenetrableObstacle (newEnemy->v.origin)) {
|
||||
|
||||
|
|
@ -579,7 +579,7 @@ Vector Bot::getEnemyBodyOffset () {
|
|||
auto headshotPct = conf.getDifficultyTweaks (m_difficulty)->headshotPct;
|
||||
|
||||
// with to much recoil or using specific weapons choice to aim to the chest
|
||||
if (distance > kSprayDistance && (isRecoilHigh () || usesSniperAWP () || usesShotgun ())) {
|
||||
if (distance > kSprayDistance && (isRecoilHigh () || usesShotgun ())) {
|
||||
headshotPct = 0;
|
||||
}
|
||||
|
||||
|
|
@ -587,6 +587,10 @@ Vector Bot::getEnemyBodyOffset () {
|
|||
if (m_enemyBodyPartSet == m_enemy || rg.chance (headshotPct)) {
|
||||
spot = m_enemyOrigin + getCustomHeight (distance);
|
||||
|
||||
if (usesSniper ()) {
|
||||
spot.z -= pev->view_ofs.z * 0.5f;
|
||||
}
|
||||
|
||||
// set's the enemy shooting spot to head, if headshot pct allows, and use head for that
|
||||
// enemy until new enemy is acquired, to prevent too shaky aiming
|
||||
m_enemyBodyPartSet = m_enemy;
|
||||
|
|
@ -698,8 +702,6 @@ bool Bot::isPenetrableObstacle (const Vector &dest) {
|
|||
// this function returns true if enemy can be shoot through some obstacle, false otherwise.
|
||||
// credits goes to Immortal_BLG
|
||||
|
||||
const auto method = cv_shoots_thru_walls.int_ ();
|
||||
|
||||
if (m_isUsingGrenade || m_difficulty < Difficulty::Normal) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -708,6 +710,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) {
|
|||
if (penetratePower == 0) {
|
||||
return false;
|
||||
}
|
||||
const auto method = cv_shoots_thru_walls.int_ ();
|
||||
|
||||
// switch methods
|
||||
switch (method) {
|
||||
|
|
@ -989,7 +992,10 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
}
|
||||
}
|
||||
}
|
||||
m_shootTime = game.time ();
|
||||
|
||||
if (pev->button & IN_ATTACK) {
|
||||
m_shootTime = game.time ();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// don't attack with knife over long distance
|
||||
|
|
@ -1239,8 +1245,7 @@ void Bot::attackMovement () {
|
|||
|
||||
// only take cover when bomb is not planted and enemy can see the bot or the bot is VIP
|
||||
if (!game.is (GameFlags::CSDM)) {
|
||||
if ((m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
|
||||
m_moveSpeed = -pev->maxspeed;
|
||||
if (m_retreatTime < game.time () && (m_states & Sense::SeeingEnemy) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) {
|
||||
startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true);
|
||||
}
|
||||
else if (approach < 50) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
ConVar cv_display_menu_text ("display_menu_text", "1", "Enables or disables display menu text, when players asks for menu. Useful only for Android.", true, 0.0f, 1.0f, Var::Xash3D);
|
||||
ConVar cv_password ("password", "", "The value (password) for the setinfo key, if user sets correct password, he's gains access to bot commands and menus.", false, 0.0f, 0.0f, Var::Password);
|
||||
ConVar cv_password_key ("password_key", "_ybpw", "The name of setinfo key used to store password to bot commands and menus.", false);
|
||||
ConVar cv_bots_kill_on_endround ("bots_kill_on_endround", "0", "Allows to use classic bot kill on issuing end-round command in menus, instead of gamedll endround.", false);
|
||||
|
||||
int BotControl::cmdAddBot () {
|
||||
enum args { alias = 1, difficulty, personality, team, model, name, max };
|
||||
|
|
@ -1025,7 +1026,7 @@ int BotControl::menuMain (int item) {
|
|||
break;
|
||||
|
||||
case 4:
|
||||
if (game.is (GameFlags::ReGameDLL)) {
|
||||
if (game.is (GameFlags::ReGameDLL) && !cv_bots_kill_on_endround.bool_ ()) {
|
||||
game.serverCommand ("endround");
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -1151,7 +1151,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
|
|||
}
|
||||
m_basePing = rg.get (cv_ping_base_min.int_ (), cv_ping_base_max.int_ ());
|
||||
|
||||
m_lastCommandTime = game.time () - 0.1f;
|
||||
m_previousThinkTime = game.time () - 0.1f;
|
||||
m_frameInterval = game.time ();
|
||||
m_heavyTimestamp = game.time ();
|
||||
m_slowFrameTimestamp = 0.0f;
|
||||
|
|
@ -1620,7 +1620,7 @@ void Bot::newRound () {
|
|||
if (rg.chance (50)) {
|
||||
pushChatterMessage (Chatter::NewRound);
|
||||
}
|
||||
auto thinkFps = cr::clamp (cv_think_fps.float_ (), 24.0f, 90.0f);
|
||||
auto thinkFps = cr::clamp (cv_think_fps.float_ (), 30.0f, 90.0f);
|
||||
auto updateInterval = 1.0f / thinkFps;
|
||||
|
||||
if (game.is (GameFlags::Xash3D)) {
|
||||
|
|
@ -1634,7 +1634,8 @@ void Bot::newRound () {
|
|||
else if (game.is (GameFlags::Legacy)) {
|
||||
updateInterval = 0.0f; // legacy games behaves strange, when this enabled
|
||||
}
|
||||
m_updateInterval = updateInterval;
|
||||
m_thinkDelay.interval = updateInterval;
|
||||
m_commandDelay.interval = 1.0f / 60.0f;
|
||||
}
|
||||
|
||||
void Bot::resetPathSearchType () {
|
||||
|
|
|
|||
|
|
@ -820,9 +820,26 @@ void Bot::moveToGoal () {
|
|||
|
||||
// press duck button if we need to
|
||||
if (m_pathFlags & NodeFlag::Crouch) {
|
||||
constexpr auto kEndRouteThreshold = 3;
|
||||
bool pressDuck = true;
|
||||
|
||||
if (!(m_pathFlags & (NodeFlag::Camp | NodeFlag::Goal)) || m_pathWalk.length () < kEndRouteThreshold) {
|
||||
if (m_pathFlags & (NodeFlag::Camp | NodeFlag::Goal)) {
|
||||
TraceResult tr {};
|
||||
|
||||
auto src = m_path->origin;
|
||||
auto dst = m_path->origin;
|
||||
|
||||
src.z += 12.0f;
|
||||
dst.z += 18.0f + 28.0f;
|
||||
|
||||
game.testLine (src, dst, TraceIgnore::Everything, ent (), &tr);
|
||||
|
||||
if (tr.flFraction >= 0.95f) {
|
||||
pressDuck = false;
|
||||
}
|
||||
}
|
||||
|
||||
// press duck if not canceled by visibility count check only and it's end of the route
|
||||
if (pressDuck) {
|
||||
pev->button |= IN_DUCK;
|
||||
}
|
||||
}
|
||||
|
|
@ -899,7 +916,7 @@ bool Bot::updateNavigation () {
|
|||
|
||||
m_navTimeset = game.time ();
|
||||
}
|
||||
m_destOrigin = m_pathOrigin + pev->view_ofs;
|
||||
m_destOrigin = m_pathOrigin;
|
||||
|
||||
// this node has additional travel flags - care about them
|
||||
if (m_currentTravelFlags & PathFlag::Jump) {
|
||||
|
|
@ -962,7 +979,7 @@ bool Bot::updateNavigation () {
|
|||
const float ladderDistance = pev->origin.distance (m_pathOrigin);
|
||||
|
||||
if (m_pathOrigin.z >= pev->origin.z + 16.0f) {
|
||||
constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 16.0f);
|
||||
constexpr auto kLadderOffset = Vector (0.0f, 0.0f, 36.0f);
|
||||
|
||||
m_pathOrigin = m_path->origin + kLadderOffset;
|
||||
}
|
||||
|
|
@ -979,13 +996,16 @@ bool Bot::updateNavigation () {
|
|||
const auto prevNodeIndex = m_previousNodes[0];
|
||||
|
||||
// do a precise movement when very near
|
||||
if (!isDucking () && graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
|
||||
m_moveSpeed = pev->maxspeed * 0.4f;
|
||||
if (graph.exists (prevNodeIndex) && !(graph[prevNodeIndex].flags & NodeFlag::Ladder) && ladderDistance < 64.0f) {
|
||||
if (!isDucking ()) {
|
||||
m_moveSpeed = pev->maxspeed * 0.4f;
|
||||
}
|
||||
|
||||
// do not duck while not on ladder
|
||||
if (!isOnLadder ()) {
|
||||
pev->button &= ~IN_DUCK;
|
||||
}
|
||||
m_approachingLadderTimer.start (m_frameInterval * 4.0f);
|
||||
}
|
||||
|
||||
// special detection if someone is using the ladder (to prevent to have bots-towers on ladders)
|
||||
|
|
@ -1133,7 +1153,7 @@ bool Bot::updateNavigation () {
|
|||
}
|
||||
}
|
||||
else if (m_pathFlags & NodeFlag::Ladder) {
|
||||
desiredDistanceSq = cr::sqrf (8.0f);
|
||||
desiredDistanceSq = cr::sqrf (16.0f);
|
||||
}
|
||||
else if (m_currentTravelFlags & PathFlag::Jump) {
|
||||
desiredDistanceSq = 0.0f;
|
||||
|
|
@ -1142,7 +1162,7 @@ bool Bot::updateNavigation () {
|
|||
desiredDistanceSq = 0.0f;
|
||||
}
|
||||
else if (isOccupiedNode (m_path->number)) {
|
||||
desiredDistanceSq = cr::sqrf (96.0f);
|
||||
desiredDistanceSq = cr::sqrf (102.0f);
|
||||
}
|
||||
else {
|
||||
desiredDistanceSq = cr::max (cr::sqrf (m_path->radius), desiredDistanceSq);
|
||||
|
|
@ -1157,9 +1177,9 @@ bool Bot::updateNavigation () {
|
|||
}
|
||||
|
||||
// needs precise placement - check if we get past the point
|
||||
if (desiredDistanceSq < cr::sqrf (22.0f)
|
||||
if (desiredDistanceSq < cr::sqrf (16.0f)
|
||||
&& nodeDistanceSq < cr::sqrf (30.0f)
|
||||
&& m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= nodeDistanceSq) {
|
||||
&& m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) > nodeDistanceSq) {
|
||||
|
||||
desiredDistanceSq = nodeDistanceSq + 1.0f;
|
||||
}
|
||||
|
|
@ -3127,7 +3147,7 @@ bool Bot::isReachableNode (int index) {
|
|||
const Vector &dst = graph[index].origin;
|
||||
|
||||
// is the destination close enough?
|
||||
if (dst.distanceSq (src) >= cr::sqrf (600.0f)) {
|
||||
if (dst.distanceSq (src) >= cr::sqrf (400.0f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,15 @@ template <typename U> bool BotStorage::load (SmallArray <U> &data, ExtenHeader *
|
|||
// tell graph about exten header
|
||||
graph.setExtenHeader (&extenHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for visibility tables load counts of stand/count numbers
|
||||
if (type.option & StorageOption::Vistable) {
|
||||
for (auto &path : graph) {
|
||||
file.read (&path.vis, sizeof (PathVis));
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast <float> (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f);
|
||||
file.close ();
|
||||
|
||||
|
|
@ -248,7 +256,14 @@ template <typename U> bool BotStorage::save (const SmallArray <U> &data, ExtenHe
|
|||
hdr.uncompressed = static_cast <int32_t> (rawLength);
|
||||
|
||||
file.write (&hdr, sizeof (StorageHeader));
|
||||
file.write (compressed.data (), sizeof (uint8_t), compressedLength);
|
||||
file.write (compressed.data (), sizeof (uint8_t), compressedLength);
|
||||
|
||||
// for visibility tables save counts of stand/count numbers
|
||||
if (type.option & StorageOption::Vistable) {
|
||||
for (auto &path : graph) {
|
||||
file.write (&path.vis, sizeof (PathVis));
|
||||
}
|
||||
}
|
||||
|
||||
// add extension
|
||||
if ((type.option & StorageOption::Exten) && exten != nullptr) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ void Bot::normal_ () {
|
|||
if (m_currentNodeIndex == debugGoal) {
|
||||
const auto &debugOrigin = graph[debugGoal].origin;
|
||||
|
||||
if (debugOrigin.distanceSq (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) {
|
||||
if (debugOrigin.distanceSq2d (pev->origin) < cr::sqrf (22.0f) && util.isVisible (debugOrigin, ent ())) {
|
||||
m_moveToGoal = false;
|
||||
m_checkTerrain = false;
|
||||
|
||||
|
|
@ -468,10 +468,10 @@ void Bot::seekCover_ () {
|
|||
destIndex = getTask ()->data;
|
||||
}
|
||||
else {
|
||||
destIndex = findCoverNode (usesSniper () ? 256.0f : 512.0f);
|
||||
destIndex = findCoverNode (900.0f);
|
||||
|
||||
if (destIndex == kInvalidNodeIndex) {
|
||||
m_retreatTime = game.time () + rg.get (5.0f, 10.0f);
|
||||
m_retreatTime = game.time () + rg.get (1.0f, 2.0f);
|
||||
m_prevGoalIndex = kInvalidNodeIndex;
|
||||
|
||||
completeTask ();
|
||||
|
|
|
|||
|
|
@ -175,33 +175,36 @@ void Bot::setAimDirection () {
|
|||
m_lookAt = m_lookAtSafe;
|
||||
}
|
||||
else if (flags & AimFlags::Nav) {
|
||||
m_lookAt = m_destOrigin;
|
||||
const auto &destOrigin = m_destOrigin + pev->view_ofs;
|
||||
m_lookAt = destOrigin;
|
||||
|
||||
|
||||
if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time ()
|
||||
&& !m_isStuck && m_moveSpeed > getShiftSpeed ()
|
||||
&& !(pev->button & IN_DUCK)
|
||||
&& !m_isStuck && !(pev->button & IN_DUCK)
|
||||
&& m_currentNodeIndex != kInvalidNodeIndex
|
||||
&& !(m_pathFlags & (NodeFlag::Ladder | NodeFlag::Crouch))
|
||||
&& m_pathWalk.hasNext ()
|
||||
&& pev->origin.distanceSq (m_destOrigin) < cr::sqrf (512.0f)) {
|
||||
&& m_pathWalk.hasNext () && !isOnLadder ()
|
||||
&& pev->origin.distanceSq (destOrigin) < cr::sqrf (512.0f)) {
|
||||
|
||||
const auto nextPathIndex = m_pathWalk.next ();
|
||||
const auto doubleNextPath = m_pathWalk.doubleNext ();
|
||||
|
||||
if (vistab.visible (m_currentNodeIndex, doubleNextPath)) {
|
||||
m_lookAt = graph[doubleNextPath].origin + pev->view_ofs;
|
||||
const auto &gn = graph[doubleNextPath];
|
||||
m_lookAt = gn.origin + pev->view_ofs;
|
||||
}
|
||||
else if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {
|
||||
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs;
|
||||
const auto &gn = graph[nextPathIndex];
|
||||
m_lookAt = gn.origin + pev->view_ofs;
|
||||
}
|
||||
else {
|
||||
m_lookAt = m_destOrigin;
|
||||
m_lookAt = pev->origin + pev->view_ofs + pev->v_angle.forward () * 300.0f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_lookAt = m_destOrigin;
|
||||
m_lookAt = destOrigin;
|
||||
}
|
||||
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || !cr::fzero (pev->velocity.z);
|
||||
const bool horizontalMovement = (m_pathFlags & NodeFlag::Ladder) || isOnLadder ();
|
||||
|
||||
if (m_canChooseAimDirection && m_seeEnemyTime + 4.0f < game.time () && m_currentNodeIndex != kInvalidNodeIndex && !horizontalMovement) {
|
||||
const auto dangerIndex = practice.getIndex (m_team, m_currentNodeIndex, m_currentNodeIndex);
|
||||
|
|
@ -211,7 +214,7 @@ void Bot::setAimDirection () {
|
|||
&& !(graph[dangerIndex].flags & NodeFlag::Crouch)) {
|
||||
|
||||
if (pev->origin.distanceSq (graph[dangerIndex].origin) < cr::sqrf (512.0f)) {
|
||||
m_lookAt = m_destOrigin;
|
||||
m_lookAt = destOrigin;
|
||||
}
|
||||
else {
|
||||
m_lookAt = graph[dangerIndex].origin + pev->view_ofs;
|
||||
|
|
@ -225,23 +228,20 @@ void Bot::setAimDirection () {
|
|||
// try look at next node if on ladder
|
||||
if (horizontalMovement && m_pathWalk.hasNext ()) {
|
||||
const auto &nextPath = graph[m_pathWalk.next ()];
|
||||
|
||||
|
||||
if ((nextPath.flags & NodeFlag::Ladder) && m_destOrigin.distanceSq (pev->origin) < cr::sqrf (128.0f) && nextPath.origin.z > m_pathOrigin.z + 26.0f) {
|
||||
m_lookAt = nextPath.origin;
|
||||
}
|
||||
else {
|
||||
m_lookAt = m_destOrigin;
|
||||
m_lookAt = nextPath.origin + pev->view_ofs;
|
||||
}
|
||||
}
|
||||
|
||||
// don't look at bottom of node, if reached it
|
||||
if (m_lookAt == destOrigin && !horizontalMovement) {
|
||||
m_lookAt.z = getEyesPos ().z;
|
||||
}
|
||||
|
||||
// try to look at last victim for a little, maybe there's some one else
|
||||
if (game.isNullEntity (m_enemy) && m_difficulty >= Difficulty::Normal && !m_forgetLastVictimTimer.elapsed () && !m_lastVictimOrigin.empty ()) {
|
||||
m_lookAt = m_lastVictimOrigin;
|
||||
}
|
||||
|
||||
// don't look at bottom of node, if reached it
|
||||
if (m_lookAt == m_destOrigin && !horizontalMovement) {
|
||||
m_lookAt.z = getEyesPos ().z;
|
||||
m_lookAt = m_lastVictimOrigin + pev->view_ofs;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -402,21 +402,15 @@ void Bot::updateLookAngles () {
|
|||
updateBodyAngles ();
|
||||
return;
|
||||
}
|
||||
float aimSkill = cr::clamp (static_cast <float> (m_difficulty), 3.0f, 4.0f) * 25.0f;
|
||||
const bool importantAimFlags = (m_aimFlags & (AimFlags::Enemy | AimFlags::Grenade));
|
||||
|
||||
// do not slowdown while on ladder
|
||||
if (isOnLadderPath () || isOnLadder ()) {
|
||||
aimSkill = 100.0f;
|
||||
}
|
||||
const bool importantAimFlags = (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade));
|
||||
|
||||
float accelerate = aimSkill * 30.0f;
|
||||
float stiffness = aimSkill * 2.0f;
|
||||
float damping = aimSkill * 0.25f;
|
||||
float accelerate = 3000.0f;
|
||||
float stiffness = 200.0f;
|
||||
float damping = 25.0f;
|
||||
|
||||
if ((importantAimFlags || m_wantsToFire) && m_difficulty > Difficulty::Normal) {
|
||||
if (m_difficulty == Difficulty::Expert) {
|
||||
accelerate += 600.0f;
|
||||
accelerate += 300.0f;
|
||||
}
|
||||
stiffness += 100.0f;
|
||||
damping -= 5.0f;
|
||||
|
|
@ -427,8 +421,8 @@ void Bot::updateLookAngles () {
|
|||
float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
|
||||
|
||||
// prevent reverse facing angles when navigating normally
|
||||
if (m_difficulty < Difficulty::Easy && m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) {
|
||||
const float forward = (m_pathOrigin - pev->origin).yaw ();
|
||||
if (m_moveToGoal && !importantAimFlags && !m_pathOrigin.empty ()) {
|
||||
const float forward = (m_lookAt - pev->origin).yaw ();
|
||||
|
||||
if (!cr::fzero (forward)) {
|
||||
const float current = cr::wrapAngle (pev->v_angle.y - forward);
|
||||
|
|
@ -460,9 +454,11 @@ void Bot::updateLookAngles () {
|
|||
const float accel = cr::clamp (2.0f * stiffness * angleDiffPitch - damping * m_lookPitchVel, -accelerate, accelerate);
|
||||
|
||||
m_lookPitchVel += delta * accel;
|
||||
m_idealAngles.x += cr::clamp (delta * m_lookPitchVel, -89.0f, 89.0f);
|
||||
m_idealAngles.x += delta * m_lookPitchVel;
|
||||
|
||||
pev->v_angle = m_idealAngles.clampAngles ();
|
||||
m_idealAngles.x = cr::clamp (m_idealAngles.x, -89.0f, 89.0f);
|
||||
|
||||
pev->v_angle = m_idealAngles;
|
||||
pev->v_angle.z = 0.0f;
|
||||
|
||||
updateBodyAngles ();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue