fix: crash on predicted node index use after wiped

refactor: reworked prediction to calc prediction for all bots at one job, instead of firing it for every bot
This commit is contained in:
jeefo 2023-06-29 20:17:46 +03:00
commit 93d9187f6d
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
12 changed files with 205 additions and 182 deletions

View file

@ -404,17 +404,6 @@ CR_DECLARE_SCOPED_ENUM (GoalTactic,
RescueHostage RescueHostage
) )
// frustum sides
CR_DECLARE_SCOPED_ENUM (FrustumSide,
Top = 0,
Bottom,
Left,
Right,
Near,
Far,
Num
)
// some hard-coded desire defines used to override calculated ones // some hard-coded desire defines used to override calculated ones
namespace TaskPri { namespace TaskPri {
constexpr auto Normal { 35.0f }; constexpr auto Normal { 35.0f };

View file

@ -17,36 +17,6 @@ struct BotRequest {
String name; String name;
}; };
// initial frustum data
struct FrustumData : public Singleton <FrustumData> {
private:
float Fov = 75.0f;
float AspectRatio = 1.33333f;
public:
float MaxView = 4096.0f;
float MinView = 2.0f;
public:
float farHeight; // height of the far frustum
float farWidth; // width of the far frustum
float nearHeight; // height of the near frustum
float nearWidth; // width of the near frustum
public:
FrustumData () {
nearHeight = 2.0f * cr::tanf (Fov * 0.0174532925f * 0.5f) * MinView;
nearWidth = nearHeight * AspectRatio;
farHeight = 2.0f * cr::tanf (Fov * 0.0174532925f * 0.5f) * MaxView;
farWidth = farHeight * AspectRatio;
}
};
// declare global frustum data
CR_EXPOSE_GLOBAL_SINGLETON (FrustumData, frustum);
// manager class // manager class
class BotManager final : public Singleton <BotManager> { class BotManager final : public Singleton <BotManager> {
public: public:
@ -68,6 +38,7 @@ private:
float m_lastChatTime {}; // global chat time timestamp float m_lastChatTime {}; // global chat time timestamp
float m_timeBombPlanted {}; // time the bomb were planted float m_timeBombPlanted {}; // time the bomb were planted
float m_lastRadioTime[kGameTeamNum] {}; // global radio time float m_lastRadioTime[kGameTeamNum] {}; // global radio time
float m_predictUpdateTime {}; // time to update prediction entity
int m_lastWinner {}; // the team who won previous round int m_lastWinner {}; // the team who won previous round
int m_lastDifficulty {}; // last bots difficulty int m_lastDifficulty {}; // last bots difficulty
@ -89,7 +60,6 @@ private:
SmallArray <UniqueBot> m_bots {}; // all available bots SmallArray <UniqueBot> m_bots {}; // all available bots
edict_t *m_killerEntity {}; // killer entity for bots edict_t *m_killerEntity {}; // killer entity for bots
FrustumData m_frustumData {};
protected: protected:
BotCreateResult create (StringRef name, int difficulty, int personality, int team, int skin); BotCreateResult create (StringRef name, int difficulty, int personality, int team, int skin);
@ -152,6 +122,8 @@ public:
void handleDeath (edict_t *killer, edict_t *victim); void handleDeath (edict_t *killer, edict_t *victim);
void setLastWinner (int winner); void setLastWinner (int winner);
void checkBotModel (edict_t *ent, char *infobuffer); void checkBotModel (edict_t *ent, char *infobuffer);
void syncUpdateBotsPredict ();
void updateBotsPredict ();
bool isTeamStacked (int team); bool isTeamStacked (int team);
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);
@ -318,12 +290,17 @@ public:
public: public:
template <typename F> void enqueue (F &&fn) { template <typename F> void enqueue (F &&fn) {
if (m_botWorker.threadCount () == 0) { if (!available ()) {
fn (); // no threads, no fun, just run task in current thread fn (); // no threads, no fun, just run task in current thread
return; return;
} }
m_botWorker.enqueue (cr::move (fn)); m_botWorker.enqueue (cr::move (fn));
} }
public:
bool available () {
return m_botWorker.threadCount () > 0;
}
}; };
// expose global // expose global

View file

@ -99,9 +99,6 @@ public:
// installs the sendto function interception // installs the sendto function interception
void installSendTo (); void installSendTo ();
// check if object inside frustum plane
bool isObjectInsidePlane (FrustumPlane &plane, const Vector &center, float height, float radius);
// checks if same model omitting the models directory // checks if same model omitting the models directory
bool isModel (const edict_t *ent, StringRef model); bool isModel (const edict_t *ent, StringRef model);

65
inc/vision.h Normal file
View file

@ -0,0 +1,65 @@
//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// SPDX-License-Identifier: MIT
//
#pragma once
// view frustum for bots
class Frustum : public Singleton <Frustum> {
public:
struct Plane {
Vector normal {};
Vector point {};
float result {};
};
enum class PlaneSide : int {
Top = 0,
Bottom,
Left,
Right,
Near,
Far,
Num
};
public:
using Planes = Plane[static_cast <int> (PlaneSide::Num)];
private:
static constexpr float kFov = 75.0f;
static constexpr float kAspectRatio = 16.0f / 9.0f;
static constexpr float kMaxViewDistance = 4096.0f;
static constexpr float kMinViewDistance = 2.0f;
private:
float m_farHeight; // height of the far frustum
float m_farWidth; // width of the far frustum
float m_nearHeight; // height of the near frustum
float m_nearWidth; // width of the near frustum
public:
explicit Frustum () {
m_nearHeight = 2.0f * cr::tanf (kFov * deg2rad (1.0f) * 0.5f) * kMinViewDistance;
m_nearWidth = m_nearHeight * kAspectRatio;
m_farHeight = 2.0f * cr::tanf (kFov * deg2rad (1.0f) * 0.5f) * kMaxViewDistance;
m_farWidth = m_farHeight * kAspectRatio;
}
public:
// updates bot view frustum
void calculate (Planes &planes, const Vector &viewAngle, const Vector &viewOffset);
// check if object inside frustum plane
bool isObjectInsidePlane (const Plane &plane, const Vector &center, float height, float radius) const;
// check if entity origin inside view plane
bool check (const Planes &planes, edict_t *ent) const;
};
// declare global frustum data
CR_EXPOSE_GLOBAL_SINGLETON (Frustum, frustum);

View file

@ -112,13 +112,6 @@ struct ClientNoise {
float last; float last;
}; };
// defines frustum data for bot
struct FrustumPlane {
Vector normal;
Vector point;
float result;
};
// array of clients struct // array of clients struct
struct Client { struct Client {
edict_t *ent; // pointer to actual edict edict_t *ent; // pointer to actual edict
@ -146,6 +139,7 @@ struct ChatCollection {
// include bot graph stuff // include bot graph stuff
#include <graph.h> #include <graph.h>
#include <vision.h>
// this structure links nodes returned from pathfinder // this structure links nodes returned from pathfinder
class PathWalk final : public NonCopyable { class PathWalk final : public NonCopyable {
@ -224,8 +218,6 @@ public:
private: private:
mutable Mutex m_pathFindLock {}; mutable Mutex m_pathFindLock {};
mutable Mutex m_predictLock {};
mutable Mutex m_lookAnglesLock {};
private: private:
uint32_t m_states {}; // sensing bitstates uint32_t m_states {}; // sensing bitstates
@ -374,7 +366,7 @@ private:
Path *m_path {}; // pointer to the current path node Path *m_path {}; // pointer to the current path node
String m_chatBuffer {}; // space for strings (say text...) String m_chatBuffer {}; // space for strings (say text...)
FrustumPlane m_frustum[FrustumSide::Num] {}; Frustum::Planes m_viewFrustum {};
private: private:
int pickBestWeapon (Array <int> &vec, int moneySave); int pickBestWeapon (Array <int> &vec, int moneySave);
@ -444,7 +436,6 @@ private:
bool isPenetrableObstacle2 (const Vector &dest); bool isPenetrableObstacle2 (const Vector &dest);
bool isPenetrableObstacle3 (const Vector &dest); bool isPenetrableObstacle3 (const Vector &dest);
bool isEnemyBehindShield (edict_t *enemy); bool isEnemyBehindShield (edict_t *enemy);
bool isEnemyInFrustum (edict_t *enemy);
bool checkChatKeywords (String &reply); bool checkChatKeywords (String &reply);
bool isReplyingToChat (); bool isReplyingToChat ();
bool isReachableNode (int index); bool isReachableNode (int index);
@ -472,7 +463,6 @@ private:
void checkBurstMode (float distance); void checkBurstMode (float distance);
void checkSilencer (); void checkSilencer ();
void updateAimDir (); void updateAimDir ();
void syncUpdateLookAngles ();
void updateLookAngles (); void updateLookAngles ();
void updateBodyAngles (); void updateBodyAngles ();
void updateLookAnglesNewbie (const Vector &direction, float delta); void updateLookAnglesNewbie (const Vector &direction, float delta);
@ -487,7 +477,6 @@ private:
void updatePracticeValue (int damage); void updatePracticeValue (int damage);
void updatePracticeDamage (edict_t *attacker, int damage); void updatePracticeDamage (edict_t *attacker, int damage);
void findShortestPath (int srcIndex, int destIndex); void findShortestPath (int srcIndex, int destIndex);
void calculateFrustum ();
void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast); void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast);
void syncFindPath (int srcIndex, int destIndex, FindPath pathType); void syncFindPath (int srcIndex, int destIndex, FindPath pathType);
void debugMsgInternal (const char *str); void debugMsgInternal (const char *str);
@ -511,8 +500,6 @@ private:
void selectSecondary (); void selectSecondary ();
void selectWeaponById (int id); void selectWeaponById (int id);
void selectWeaponByIndex (int index); void selectWeaponByIndex (int index);
void refreshEnemyPredict ();
void syncUpdatePredictedIndex ();
void updatePredictedIndex (); void updatePredictedIndex ();
void refreshModelName (char *infobuffer); void refreshModelName (char *infobuffer);
@ -523,6 +510,7 @@ private:
void translateInput (); void translateInput ();
void moveToGoal (); void moveToGoal ();
void resetMovement (); void resetMovement ();
void refreshEnemyPredict ();
void normal_ (); void normal_ ();
void spraypaint_ (); void spraypaint_ ();
@ -704,7 +692,6 @@ public:
// need to wait until all threads will finish it's work before terminating bot object // need to wait until all threads will finish it's work before terminating bot object
~Bot () { ~Bot () {
MutexScopedLock lock1 (m_pathFindLock); MutexScopedLock lock1 (m_pathFindLock);
MutexScopedLock lock2 (m_lookAnglesLock);
} }
public: public:

View file

@ -1623,17 +1623,12 @@ void Bot::overrideConditions () {
} }
} }
void Bot::syncUpdatePredictedIndex () { void Bot::updatePredictedIndex () {
auto wipePredict = [this] () { auto wipePredict = [this] () {
m_lastPredictIndex = kInvalidNodeIndex; m_lastPredictIndex = kInvalidNodeIndex;
m_lastPredictLength = kInfiniteDistanceLong; m_lastPredictLength = kInfiniteDistanceLong;
}; };
if (!m_predictLock.tryLock ()) {
return; // allow only single instance of search per-bot
}
ScopedUnlock <Mutex> unlock (m_predictLock);
const auto lastEnemyOrigin = m_lastEnemyOrigin; const auto lastEnemyOrigin = m_lastEnemyOrigin;
const auto currentNodeIndex = m_currentNodeIndex; const auto currentNodeIndex = m_currentNodeIndex;
const auto &botOrigin = pev->origin; const auto &botOrigin = pev->origin;
@ -1671,15 +1666,6 @@ void Bot::syncUpdatePredictedIndex () {
wipePredict (); wipePredict ();
} }
void Bot::updatePredictedIndex () {
if (m_lastEnemyOrigin.empty ()) {
return; // do not run task if no last enemy
}
worker.enqueue ([this] () {
syncUpdatePredictedIndex ();
});
}
void Bot::refreshEnemyPredict () { void Bot::refreshEnemyPredict () {
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) {
const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin); const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
@ -1693,10 +1679,6 @@ void Bot::refreshEnemyPredict () {
m_aimFlags |= AimFlags::LastEnemy; m_aimFlags |= AimFlags::LastEnemy;
} }
} }
if (m_aimFlags & AimFlags::PredictPath) {
updatePredictedIndex ();
}
} }
void Bot::setConditions () { void Bot::setConditions () {

View file

@ -212,7 +212,7 @@ bool Bot::seesEnemy (edict_t *player) {
return false; return false;
} }
if (isEnemyInFrustum (player) && isInViewCone (player->v.origin) && checkBodyParts (player)) { if (isInViewCone (player->v.origin) && frustum.check (m_viewFrustum, player) && checkBodyParts (player)) {
m_seeEnemyTime = game.time (); m_seeEnemyTime = game.time ();
m_lastEnemy = player; m_lastEnemy = player;
m_lastEnemyOrigin = m_enemyOrigin; m_lastEnemyOrigin = m_enemyOrigin;
@ -277,7 +277,7 @@ bool Bot::lookupEnemies () {
} }
// check the engine PVS // check the engine PVS
if (!isEnemyInFrustum (interesting) || !game.checkVisibility (interesting, set)) { if (!frustum.check (m_viewFrustum, interesting) || !game.checkVisibility (interesting, set)) {
continue; continue;
} }
@ -303,7 +303,7 @@ bool Bot::lookupEnemies () {
player = client.ent; player = client.ent;
// check the engine PVS // check the engine PVS
if (!isEnemyInFrustum (player) || !game.checkVisibility (player, set)) { if (!frustum.check (m_viewFrustum, player) || !game.checkVisibility (player, set)) {
continue; continue;
} }

View file

@ -75,8 +75,6 @@ BotManager::BotManager () {
m_botsCanPause = false; m_botsCanPause = false;
m_roundOver = false; m_roundOver = false;
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
for (int i = 0; i < kGameTeamNum; ++i) { for (int i = 0; i < kGameTeamNum; ++i) {
m_leaderChoosen[i] = false; m_leaderChoosen[i] = false;
m_economicsGood[i] = true; m_economicsGood[i] = true;
@ -297,6 +295,9 @@ void BotManager::frame () {
for (const auto &bot : m_bots) { for (const auto &bot : m_bots) {
bot->frame (); bot->frame ();
} }
// run prediction for bots
updateBotsPredict ();
} }
void BotManager::addbot (StringRef name, int difficulty, int personality, int team, int skin, bool manual) { void BotManager::addbot (StringRef name, int difficulty, int personality, int team, int skin, bool manual) {
@ -764,6 +765,30 @@ void BotManager::checkBotModel (edict_t *ent, char *infobuffer) {
} }
} }
void BotManager::syncUpdateBotsPredict () {
if (m_predictUpdateTime > game.time ()) {
return;
}
// update predicted index for all the bots
for (const auto &bot : m_bots) {
if (!bot.get ()) {
continue;
}
if (bot->m_notKilled && (bot->m_aimFlags & AimFlags::PredictPath)) {
bot->updatePredictedIndex ();
}
}
m_predictUpdateTime = game.time () + 0.1f;
}
void BotManager::updateBotsPredict () {
// push update predict task for all bots to queue
worker.enqueue ([this] {
syncUpdateBotsPredict ();
});
}
void BotManager::setWeaponMode (int selection) { void BotManager::setWeaponMode (int selection) {
// this function sets bots weapon mode // this function sets bots weapon mode
@ -1304,7 +1329,6 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
} }
} }
void Bot::newRound () { void Bot::newRound () {
// this function initializes a bot after creation & at the start of each round // this function initializes a bot after creation & at the start of each round
@ -1552,6 +1576,12 @@ void Bot::resetPathSearchType () {
if (cv_debug_goal.int_ () != kInvalidNodeIndex) { if (cv_debug_goal.int_ () != kInvalidNodeIndex) {
m_pathType = FindPath::Fast; m_pathType = FindPath::Fast;
} }
// no need to be safe on csdm
if (game.is (GameFlags::CSDM)) {
m_pathType = FindPath::Fast;
return;
}
} }
void Bot::kill () { void Bot::kill () {
@ -1969,6 +1999,7 @@ void BotManager::initRound () {
m_timeBombPlanted = 0.0f; m_timeBombPlanted = 0.0f;
m_plantSearchUpdateTime = 0.0f; m_plantSearchUpdateTime = 0.0f;
m_autoKillCheckTime = 0.0f; m_autoKillCheckTime = 0.0f;
m_predictUpdateTime = 0.0f;
m_botsCanPause = false; m_botsCanPause = false;
resetFilters (); resetFilters ();
@ -1993,33 +2024,37 @@ void BotManager::setBombPlanted (bool isPlanted) {
} }
void BotThreadWorker::shutdown () { void BotThreadWorker::shutdown () {
if (!available ()) {
return;
}
game.print ("Shutting down bot thread worker."); game.print ("Shutting down bot thread worker.");
m_botWorker.shutdown (); m_botWorker.shutdown ();
} }
void BotThreadWorker::startup (int workers) { void BotThreadWorker::startup (int workers) {
const size_t count = m_botWorker.threadCount (); String disableWorkerEnv = plat.env ("YAPB_SINGLE_THREADED");
// disable worker if requested via env variable or workers are disabled
if (workers == 0 || (!disableWorkerEnv.empty () && disableWorkerEnv == "1")) {
return;
}
const auto count = m_botWorker.threadCount ();
if (count > 0) { if (count > 0) {
logger.error ("Tried to start thread pool with existing %d threads in pool.", count); logger.error ("Tried to start thread pool with existing %d threads in pool.", count);
return; return;
} }
const auto maxThreads = plat.hardwareConcurrency ();
auto requestedThreads = workers;
int requestedThreadCount = workers; if (requestedThreads < 0 || requestedThreads >= maxThreads) {
const int hardwareConcurrency = plat.hardwareConcurrency (); requestedThreads = 1;
if (requestedThreadCount == -1) {
requestedThreadCount = hardwareConcurrency / 4;
if (requestedThreadCount == 0) {
requestedThreadCount = 1;
} }
} requestedThreads = cr::clamp (requestedThreads, 1, maxThreads - 1);
requestedThreadCount = cr::clamp (requestedThreadCount, 1, hardwareConcurrency - 1);
// notify user // notify user
game.print ("Starting up bot thread worker with %d threads.", requestedThreadCount); game.print ("Starting up bot thread worker with %d threads.", requestedThreads);
// start up the worker // start up the worker
m_botWorker.startup (static_cast <size_t> (requestedThreadCount)); m_botWorker.startup (static_cast <size_t> (requestedThreads));
} }

View file

@ -559,18 +559,6 @@ void BotSupport::installSendTo () {
} }
} }
bool BotSupport::isObjectInsidePlane (FrustumPlane &plane, const Vector &center, float height, float radius) {
auto isPointInsidePlane = [&] (const Vector &point) -> bool {
return plane.result + (plane.normal | point) >= 0.0f;
};
const Vector &test = plane.normal.get2d ();
const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
return isPointInsidePlane (top) || isPointInsidePlane (bottom);
}
bool BotSupport::isModel (const edict_t *ent, StringRef model) { bool BotSupport::isModel (const edict_t *ent, StringRef model) {
return model.startsWith (ent->v.model.chars (9)); return model.startsWith (ent->v.model.chars (9));
} }

View file

@ -138,13 +138,14 @@ void Bot::updateAimDir () {
m_lookAtPredict = nullptr; m_lookAtPredict = nullptr;
}; };
auto isPredictedIndexApplicable = [this] () -> bool {
int pathLength = m_lastPredictLength; int pathLength = m_lastPredictLength;
int predictNode = m_lastPredictIndex; int predictNode = m_lastPredictIndex;
auto isPredictedIndexApplicable = [&] () -> bool {
if (predictNode != kInvalidNodeIndex) { if (predictNode != kInvalidNodeIndex) {
if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) { if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) {
predictNode = kInvalidNodeIndex; predictNode = kInvalidNodeIndex;
pathLength = kInfiniteDistanceLong;
} }
} }
return predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ (); return predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ ();
@ -152,7 +153,7 @@ void Bot::updateAimDir () {
if (changePredictedEnemy) { if (changePredictedEnemy) {
if (isPredictedIndexApplicable ()) { if (isPredictedIndexApplicable ()) {
m_lookAtPredict = graph[m_lastPredictIndex].origin; m_lookAtPredict = graph[predictNode].origin;
m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f); m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f);
m_trackingEdict = m_lastEnemy; m_trackingEdict = m_lastEnemy;
@ -183,7 +184,7 @@ void Bot::updateAimDir () {
m_lookAt = m_destOrigin; m_lookAt = m_destOrigin;
if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(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)) { if (m_moveToGoal && m_seeEnemyTime + 4.0f < game.time () && !m_isStuck && m_moveSpeed > getShiftSpeed () && !(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)) {
auto nextPathIndex = m_pathWalk.next (); const auto nextPathIndex = m_pathWalk.next ();
if (vistab.visible (m_currentNodeIndex, nextPathIndex)) { if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {
m_lookAt = graph[nextPathIndex].origin + pev->view_ofs; m_lookAt = graph[nextPathIndex].origin + pev->view_ofs;
@ -276,55 +277,6 @@ void Bot::checkDarkness () {
m_checkDarkTime = game.time () + rg.get (2.0f, 4.0f); m_checkDarkTime = game.time () + rg.get (2.0f, 4.0f);
} }
void Bot::calculateFrustum () {
// this function updates bot view frustum
Vector forward, right, up;
pev->v_angle.angleVectors (&forward, &right, &up);
static Vector fc, nc, fbl, fbr, ftl, ftr, nbl, nbr, ntl, ntr;
fc = getEyesPos () + forward * frustum.MaxView;
nc = getEyesPos () + forward * frustum.MinView;
fbl = fc + (up * frustum.farHeight * 0.5f) - (right * frustum.farWidth * 0.5f);
fbr = fc + (up * frustum.farHeight * 0.5f) + (right * frustum.farWidth * 0.5f);
ftl = fc - (up * frustum.farHeight * 0.5f) - (right * frustum.farWidth * 0.5f);
ftr = fc - (up * frustum.farHeight * 0.5f) + (right * frustum.farWidth * 0.5f);
nbl = nc + (up * frustum.nearHeight * 0.5f) - (right * frustum.nearWidth * 0.5f);
nbr = nc + (up * frustum.nearHeight * 0.5f) + (right * frustum.nearWidth * 0.5f);
ntl = nc - (up * frustum.nearHeight * 0.5f) - (right * frustum.nearWidth * 0.5f);
ntr = nc - (up * frustum.nearHeight * 0.5f) + (right * frustum.nearWidth * 0.5f);
auto setPlane = [&] (FrustumSide side, const Vector &v1, const Vector &v2, const Vector &v3) {
auto &plane = m_frustum[side];
plane.normal = ((v2 - v1) ^ (v3 - v1)).normalize ();
plane.point = v2;
plane.result = -(plane.normal | plane.point);
};
setPlane (FrustumSide::Top, ftl, ntl, ntr);
setPlane (FrustumSide::Bottom, fbr, nbr, nbl);
setPlane (FrustumSide::Left, fbl, nbl, ntl);
setPlane (FrustumSide::Right, ftr, ntr, nbr);
setPlane (FrustumSide::Near, nbr, ntr, ntl);
setPlane (FrustumSide::Far, fbl, ftl, ftr);
}
bool Bot::isEnemyInFrustum (edict_t *enemy) {
const Vector &origin = enemy->v.origin - Vector (0.0f, 0.0f, 5.0f);
for (auto &plane : m_frustum) {
if (!util.isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) {
return false;
}
}
return true;
}
void Bot::updateBodyAngles () { void Bot::updateBodyAngles () {
// set the body angles to point the gun correctly // set the body angles to point the gun correctly
pev->angles.x = -pev->v_angle.x * (1.0f / 3.0f); pev->angles.x = -pev->v_angle.x * (1.0f / 3.0f);
@ -333,21 +285,10 @@ void Bot::updateBodyAngles () {
pev->angles.clampAngles (); pev->angles.clampAngles ();
// calculate frustum plane data here, since look angles update functions call this last one // calculate frustum plane data here, since look angles update functions call this last one
calculateFrustum (); frustum.calculate (m_viewFrustum, pev->v_angle, getEyesPos ());
} }
void Bot::updateLookAngles () { void Bot::updateLookAngles () {
worker.enqueue ([this] () {
syncUpdateLookAngles ();
});
}
void Bot::syncUpdateLookAngles () {
if (!m_lookAnglesLock.tryLock ()) {
return; // allow only single instance of syncUpdateLookAngles per-bot
}
ScopedUnlock <Mutex> unlock (m_lookAnglesLock);
const float delta = cr::clamp (game.time () - m_lookUpdateTime, cr::kFloatEqualEpsilon, kViewFrameUpdate); const float delta = cr::clamp (game.time () - m_lookUpdateTime, cr::kFloatEqualEpsilon, kViewFrameUpdate);
m_lookUpdateTime = game.time (); m_lookUpdateTime = game.time ();
@ -364,10 +305,11 @@ void Bot::syncUpdateLookAngles () {
return; return;
} }
const float aimSkill = cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 25.0f;
float accelerate = 3000.0f; float accelerate = aimSkill * 30.0f;
float stiffness = 200.0f; float stiffness = aimSkill * 2.0f;
float damping = 25.0f; float damping = aimSkill * 0.25f;
if (((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) || m_wantsToFire) && m_difficulty > Difficulty::Normal) { if (((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) || m_wantsToFire) && m_difficulty > Difficulty::Normal) {
if (m_difficulty == Difficulty::Expert) { if (m_difficulty == Difficulty::Expert) {
@ -381,7 +323,7 @@ void Bot::syncUpdateLookAngles () {
const float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x); const float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x);
const float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); const float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y);
if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) { if (cr::abs (angleDiffYaw) < 1.0f) {
m_lookYawVel = 0.0f; m_lookYawVel = 0.0f;
m_idealAngles.y = direction.y; m_idealAngles.y = direction.y;
} }
@ -480,3 +422,60 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) {
pev->v_angle = pev->v_angle + delta * Vector (m_aimSpeed.x, m_aimSpeed.y, 0.0f); pev->v_angle = pev->v_angle + delta * Vector (m_aimSpeed.x, m_aimSpeed.y, 0.0f);
pev->v_angle.clampAngles (); pev->v_angle.clampAngles ();
} }
bool Frustum::isObjectInsidePlane (const Plane &plane, const Vector &center, float height, float radius) const {
auto isPointInsidePlane = [&] (const Vector &point) -> bool {
return plane.result + (plane.normal | point) >= 0.0f;
};
const Vector &test = plane.normal.get2d ();
const Vector &top = center + Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
const Vector &bottom = center - Vector (0.0f, 0.0f, height * 0.5f) + test * radius;
return isPointInsidePlane (top) || isPointInsidePlane (bottom);
}
void Frustum::calculate (Planes &planes, const Vector &viewAngle, const Vector &viewOffset) {
Vector forward, right, up;
viewAngle.angleVectors (&forward, &right, &up);
auto fc = viewOffset + forward * kMaxViewDistance;
auto nc = viewOffset + forward * kMinViewDistance;
auto fbl = fc + (up * m_farHeight * 0.5f) - (right * m_farWidth * 0.5f);
auto fbr = fc + (up * m_farHeight * 0.5f) + (right * m_farWidth * 0.5f);
auto ftl = fc - (up * m_farHeight * 0.5f) - (right * m_farWidth * 0.5f);
auto ftr = fc - (up * m_farHeight * 0.5f) + (right * m_farWidth * 0.5f);
auto nbl = nc + (up * m_nearHeight * 0.5f) - (right * m_nearWidth * 0.5f);
auto nbr = nc + (up * m_nearHeight * 0.5f) + (right * m_nearWidth * 0.5f);
auto ntl = nc - (up * m_nearHeight * 0.5f) - (right * m_nearWidth * 0.5f);
auto ntr = nc - (up * m_nearHeight * 0.5f) + (right * m_nearWidth * 0.5f);
auto setPlane = [&] (PlaneSide side, const Vector &v1, const Vector &v2, const Vector &v3) {
auto &plane = planes[static_cast <int> (side)];
plane.normal = ((v2 - v1) ^ (v3 - v1)).normalize ();
plane.point = v2;
plane.result = -(plane.normal | plane.point);
};
setPlane (PlaneSide::Top, ftl, ntl, ntr);
setPlane (PlaneSide::Bottom, fbr, nbr, nbl);
setPlane (PlaneSide::Left, fbl, nbl, ntl);
setPlane (PlaneSide::Right, ftr, ntr, nbr);
setPlane (PlaneSide::Near, nbr, ntr, ntl);
setPlane (PlaneSide::Far, fbl, ftl, ftr);
}
bool Frustum::check (const Planes &planes, edict_t *ent) const {
constexpr auto kOffset = Vector (0.0f, 0.0f, 5.0f);
const Vector &origin = ent->v.origin - kOffset;
for (const auto &plane : planes) {
if (!isObjectInsidePlane (plane, origin, 60.0f, 16.0f)) {
return false;
}
}
return true;
}

View file

@ -69,6 +69,7 @@
<ClInclude Include="..\inc\sounds.h" /> <ClInclude Include="..\inc\sounds.h" />
<ClInclude Include="..\inc\storage.h" /> <ClInclude Include="..\inc\storage.h" />
<ClInclude Include="..\inc\support.h" /> <ClInclude Include="..\inc\support.h" />
<ClInclude Include="..\inc\vision.h" />
<ClInclude Include="..\inc\vistable.h" /> <ClInclude Include="..\inc\vistable.h" />
<ClInclude Include="..\inc\yapb.h" /> <ClInclude Include="..\inc\yapb.h" />
<ClInclude Include="..\inc\version.h" /> <ClInclude Include="..\inc\version.h" />

View file

@ -183,6 +183,9 @@
<ClInclude Include="..\ext\crlib\crlib\simd\neon.h"> <ClInclude Include="..\ext\crlib\crlib\simd\neon.h">
<Filter>inc\ext\crlib\simd</Filter> <Filter>inc\ext\crlib\simd</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\inc\vision.h">
<Filter>inc</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\botlib.cpp"> <ClCompile Include="..\src\botlib.cpp">