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:
parent
5ce2032acd
commit
93d9187f6d
12 changed files with 205 additions and 182 deletions
|
|
@ -1623,17 +1623,12 @@ void Bot::overrideConditions () {
|
|||
}
|
||||
}
|
||||
|
||||
void Bot::syncUpdatePredictedIndex () {
|
||||
void Bot::updatePredictedIndex () {
|
||||
auto wipePredict = [this] () {
|
||||
m_lastPredictIndex = kInvalidNodeIndex;
|
||||
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 currentNodeIndex = m_currentNodeIndex;
|
||||
const auto &botOrigin = pev->origin;
|
||||
|
|
@ -1671,15 +1666,6 @@ void Bot::syncUpdatePredictedIndex () {
|
|||
wipePredict ();
|
||||
}
|
||||
|
||||
void Bot::updatePredictedIndex () {
|
||||
if (m_lastEnemyOrigin.empty ()) {
|
||||
return; // do not run task if no last enemy
|
||||
}
|
||||
worker.enqueue ([this] () {
|
||||
syncUpdatePredictedIndex ();
|
||||
});
|
||||
}
|
||||
|
||||
void Bot::refreshEnemyPredict () {
|
||||
if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) {
|
||||
const auto distanceToLastEnemySq = m_lastEnemyOrigin.distanceSq (pev->origin);
|
||||
|
|
@ -1693,10 +1679,6 @@ void Bot::refreshEnemyPredict () {
|
|||
m_aimFlags |= AimFlags::LastEnemy;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_aimFlags & AimFlags::PredictPath) {
|
||||
updatePredictedIndex ();
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::setConditions () {
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ bool Bot::seesEnemy (edict_t *player) {
|
|||
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_lastEnemy = player;
|
||||
m_lastEnemyOrigin = m_enemyOrigin;
|
||||
|
|
@ -277,7 +277,7 @@ bool Bot::lookupEnemies () {
|
|||
}
|
||||
|
||||
// check the engine PVS
|
||||
if (!isEnemyInFrustum (interesting) || !game.checkVisibility (interesting, set)) {
|
||||
if (!frustum.check (m_viewFrustum, interesting) || !game.checkVisibility (interesting, set)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +303,7 @@ bool Bot::lookupEnemies () {
|
|||
player = client.ent;
|
||||
|
||||
// check the engine PVS
|
||||
if (!isEnemyInFrustum (player) || !game.checkVisibility (player, set)) {
|
||||
if (!frustum.check (m_viewFrustum, player) || !game.checkVisibility (player, set)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,8 +75,6 @@ BotManager::BotManager () {
|
|||
m_botsCanPause = false;
|
||||
m_roundOver = false;
|
||||
|
||||
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
|
||||
|
||||
for (int i = 0; i < kGameTeamNum; ++i) {
|
||||
m_leaderChoosen[i] = false;
|
||||
m_economicsGood[i] = true;
|
||||
|
|
@ -297,6 +295,9 @@ void BotManager::frame () {
|
|||
for (const auto &bot : m_bots) {
|
||||
bot->frame ();
|
||||
}
|
||||
|
||||
// run prediction for bots
|
||||
updateBotsPredict ();
|
||||
}
|
||||
|
||||
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) {
|
||||
// this function sets bots weapon mode
|
||||
|
||||
|
|
@ -1304,7 +1329,6 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void Bot::newRound () {
|
||||
// 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) {
|
||||
m_pathType = FindPath::Fast;
|
||||
}
|
||||
|
||||
// no need to be safe on csdm
|
||||
if (game.is (GameFlags::CSDM)) {
|
||||
m_pathType = FindPath::Fast;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Bot::kill () {
|
||||
|
|
@ -1969,6 +1999,7 @@ void BotManager::initRound () {
|
|||
m_timeBombPlanted = 0.0f;
|
||||
m_plantSearchUpdateTime = 0.0f;
|
||||
m_autoKillCheckTime = 0.0f;
|
||||
m_predictUpdateTime = 0.0f;
|
||||
m_botsCanPause = false;
|
||||
|
||||
resetFilters ();
|
||||
|
|
@ -1993,33 +2024,37 @@ void BotManager::setBombPlanted (bool isPlanted) {
|
|||
}
|
||||
|
||||
void BotThreadWorker::shutdown () {
|
||||
if (!available ()) {
|
||||
return;
|
||||
}
|
||||
game.print ("Shutting down bot thread worker.");
|
||||
m_botWorker.shutdown ();
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.error ("Tried to start thread pool with existing %d threads in pool.", count);
|
||||
return;
|
||||
}
|
||||
const auto maxThreads = plat.hardwareConcurrency ();
|
||||
auto requestedThreads = workers;
|
||||
|
||||
int requestedThreadCount = workers;
|
||||
const int hardwareConcurrency = plat.hardwareConcurrency ();
|
||||
|
||||
if (requestedThreadCount == -1) {
|
||||
requestedThreadCount = hardwareConcurrency / 4;
|
||||
|
||||
if (requestedThreadCount == 0) {
|
||||
requestedThreadCount = 1;
|
||||
}
|
||||
if (requestedThreads < 0 || requestedThreads >= maxThreads) {
|
||||
requestedThreads = 1;
|
||||
}
|
||||
requestedThreadCount = cr::clamp (requestedThreadCount, 1, hardwareConcurrency - 1);
|
||||
requestedThreads = cr::clamp (requestedThreads, 1, maxThreads - 1);
|
||||
|
||||
// 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
|
||||
m_botWorker.startup (static_cast <size_t> (requestedThreadCount));
|
||||
m_botWorker.startup (static_cast <size_t> (requestedThreads));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -559,18 +559,6 @@ void BotSupport::installSendTo () {
|
|||
}
|
||||
}
|
||||
|
||||
bool BotSupport::isObjectInsidePlane (FrustumPlane &plane, const Vector ¢er, 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) {
|
||||
return model.startsWith (ent->v.model.chars (9));
|
||||
}
|
||||
|
|
|
|||
139
src/vision.cpp
139
src/vision.cpp
|
|
@ -138,13 +138,14 @@ void Bot::updateAimDir () {
|
|||
m_lookAtPredict = nullptr;
|
||||
};
|
||||
|
||||
auto isPredictedIndexApplicable = [this] () -> bool {
|
||||
int pathLength = m_lastPredictLength;
|
||||
int predictNode = m_lastPredictIndex;
|
||||
int pathLength = m_lastPredictLength;
|
||||
int predictNode = m_lastPredictIndex;
|
||||
|
||||
auto isPredictedIndexApplicable = [&] () -> bool {
|
||||
if (predictNode != kInvalidNodeIndex) {
|
||||
if (!vistab.visible (m_currentNodeIndex, predictNode) || !vistab.visible (m_previousNodes[0], predictNode)) {
|
||||
predictNode = kInvalidNodeIndex;
|
||||
pathLength = kInfiniteDistanceLong;
|
||||
}
|
||||
}
|
||||
return predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ ();
|
||||
|
|
@ -152,7 +153,7 @@ void Bot::updateAimDir () {
|
|||
|
||||
if (changePredictedEnemy) {
|
||||
if (isPredictedIndexApplicable ()) {
|
||||
m_lookAtPredict = graph[m_lastPredictIndex].origin;
|
||||
m_lookAtPredict = graph[predictNode].origin;
|
||||
|
||||
m_timeNextTracking = game.time () + rg.get (0.5f, 1.0f);
|
||||
m_trackingEdict = m_lastEnemy;
|
||||
|
|
@ -183,7 +184,7 @@ void Bot::updateAimDir () {
|
|||
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)) {
|
||||
auto nextPathIndex = m_pathWalk.next ();
|
||||
const auto nextPathIndex = m_pathWalk.next ();
|
||||
|
||||
if (vistab.visible (m_currentNodeIndex, nextPathIndex)) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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 () {
|
||||
// set the body angles to point the gun correctly
|
||||
pev->angles.x = -pev->v_angle.x * (1.0f / 3.0f);
|
||||
|
|
@ -333,21 +285,10 @@ void Bot::updateBodyAngles () {
|
|||
pev->angles.clampAngles ();
|
||||
|
||||
// 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 () {
|
||||
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);
|
||||
m_lookUpdateTime = game.time ();
|
||||
|
||||
|
|
@ -364,10 +305,11 @@ void Bot::syncUpdateLookAngles () {
|
|||
|
||||
return;
|
||||
}
|
||||
const float aimSkill = cr::clamp (static_cast <float> (m_difficulty), 1.0f, 4.0f) * 25.0f;
|
||||
|
||||
float accelerate = 3000.0f;
|
||||
float stiffness = 200.0f;
|
||||
float damping = 25.0f;
|
||||
float accelerate = aimSkill * 30.0f;
|
||||
float stiffness = aimSkill * 2.0f;
|
||||
float damping = aimSkill * 0.25f;
|
||||
|
||||
if (((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) || m_wantsToFire) && m_difficulty > Difficulty::Normal) {
|
||||
if (m_difficulty == Difficulty::Expert) {
|
||||
|
|
@ -381,7 +323,7 @@ void Bot::syncUpdateLookAngles () {
|
|||
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) {
|
||||
if (cr::abs (angleDiffYaw) < 1.0f) {
|
||||
m_lookYawVel = 0.0f;
|
||||
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.clampAngles ();
|
||||
}
|
||||
|
||||
bool Frustum::isObjectInsidePlane (const Plane &plane, const Vector ¢er, 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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue