nav: fix double jumping when bot gets stuck

bot: restore bot difficulty levels (ref #729)
bot: probably fix freezetime accident shooting (ref #729)
build: use noexecstack when building library
refactor: rework some old code and remove unnecessary things
This commit is contained in:
jeefo 2025-09-01 23:20:36 +03:00
commit 95f907434b
No known key found for this signature in database
GPG key ID: D696786B81B667C8
26 changed files with 157 additions and 194 deletions

View file

@ -1637,10 +1637,10 @@ Forces all the bot to vote to specified map.
强制所有人机投票给指定地图。
[ORIGINAL]
Sets the bots weapon mode to use
Sets the bots weapon mode to use.
[TRANSLATED]
设置人机的武器模式
设置人机的武器模式
[ORIGINAL]
Opens the main bot menu, or command menu if specified.

View file

@ -1400,10 +1400,10 @@ Forces all the bot to vote to specified map.
Zwingt alle Bots, für die angegebene Karte zu stimmen.
[ORIGINAL]
Sets the bots weapon mode to use
Sets the bots weapon mode to use.
[TRANSLATED]
Legt den zu verwendenden Waffenmodus des Bots fest
Legt den zu verwendenden Waffenmodus des Bots fest.
[ORIGINAL]
Opens the main bot menu, or command menu if specified.

View file

@ -9,7 +9,6 @@
hoop
-|NoS|-
Chrono
bobjones
dawgz
The BiG SMUT

View file

@ -1636,10 +1636,10 @@ Forces all the bot to vote to specified map.
Принуждает всех ботов голосовать за указанную карту.
[ORIGINAL]
Sets the bots weapon mode to use
Sets the bots weapon mode to use.
[TRANSLATED]
Настроить режим используемого ботами оружия
Настроить режим используемого ботами оружия.
[ORIGINAL]
Opens the main bot menu, or command menu if specified.

View file

@ -157,10 +157,23 @@ private:
int menuGraphPath (int item);
int menuCampDirections (int item);
int menuAutoPathDistance (int item);
int menuKickPage1 (int item);
int menuKickPage2 (int item);
int menuKickPage3 (int item);
int menuKickPage4 (int item);
int menuKickPage1 (int item) {
return menuKickPage (1, item);
}
int menuKickPage2 (int item) {
return menuKickPage (2, item);
}
int menuKickPage3 (int item) {
return menuKickPage (3, item);
}
int menuKickPage4 (int item) {
return menuKickPage (4, item);
}
int menuKickPage (int page, int item);
private:
void createMenus ();

View file

@ -77,9 +77,6 @@ public:
// bot fakeping manager
class BotFakePingManager final : public Singleton <BotFakePingManager> {
private:
mutable Mutex m_cs {};
private:
CountdownTimer m_recalcTime {};
PingBitMsg m_pbm {};

View file

@ -59,6 +59,8 @@ private:
edict_t *m_killerEntity {}; // killer entity for bots
BotTeamData m_teamData[kGameTeamNum] {}; // teams shared data
CountdownTimer m_holdQuotaManagementTimer {}; // prevent from running quota management for some time
protected:
BotCreateResult create (StringRef name, int difficulty, int personality, int team, int skin);

View file

@ -275,6 +275,10 @@ public:
return m_pathsCheckFailed;
}
void setPathsCheckFailed (const bool value) {
m_pathsCheckFailed = value;
}
public:
// do the pathfinding
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr);

View file

@ -124,7 +124,6 @@ public:
}
void setHighestDamageForTeam (int32_t team, int32_t value) {
MutexScopedLock lock (m_damageUpdateLock);
m_teamHighestDamage[team] = value;
}
};

View file

@ -58,7 +58,7 @@ public:
~Folders () = default;
public:
CTS_BUILD_STR bot { "yapb" };
CTS_BUILD_STR bot { product.nameLower };
CTS_BUILD_STR addons { "addons" };
CTS_BUILD_STR config { "conf" };
CTS_BUILD_STR data { "data" };

View file

@ -730,7 +730,6 @@ public:
Array <int32_t> m_goalHist {};
FrameDelay m_thinkTimer {};
FrameDelay m_fullThinkTimer {};
public:
Bot (edict_t *bot, int difficulty, int personality, int team, int skin);
@ -738,7 +737,6 @@ public:
public:
void logic (); /// the things that can be executed while skipping frames
void upkeep ();
void spawned ();
void takeBlind (int alpha);
void takeDamage (edict_t *inflictor, int damage, int armor, int bits);

View file

@ -149,12 +149,14 @@ if cxx == 'clang' or cxx == 'gcc'
'-fdata-sections',
'-ffunction-sections',
'-fcf-protection=none',
'-fno-plt'
'-fno-plt',
'-Wa,--noexecstack'
]
ldflags += [
'-Wl,--version-script=' + meson.project_source_root() + '/ext/ldscripts/version.lds',
'-Wl,--gc-sections'
'-Wl,--gc-sections',
'-Wl,-z,noexecstack'
]
else
cxxflags += ['-DLINKENT_STATIC']
@ -177,7 +179,7 @@ if cxx == 'clang' or cxx == 'gcc'
]
endif
else
cxxflags += ['-g3', '-ggdb', '-DDEBUG', '-D_FORTIFY_SOURCE=2']
cxxflags += ['-g3', '-ggdb', '-DDEBUG']
endif
# special script for mingw-64 builds

View file

@ -3053,13 +3053,8 @@ void Bot::frame () {
if (m_thinkTimer.time < game.time ()) {
m_thinkTimer.time = game.time () + m_thinkTimer.interval;
if (m_fullThinkTimer.time < game.time ()) {
m_fullThinkTimer.time = game.time () + m_fullThinkTimer.interval;
update ();
}
upkeep ();
}
if (m_slowFrameTimestamp > game.time ()) {
return;
@ -3182,6 +3177,7 @@ void Bot::update () {
else if (!m_botMovement) {
resetMovement ();
}
runMovement ();
}
void Bot::logicDuringFreezetime () {
@ -3199,7 +3195,7 @@ void Bot::logicDuringFreezetime () {
if (rg.chance (15) && m_jumpTime < game.time ()) {
pev->button |= IN_JUMP;
m_jumpTime = game.time () + rg (1.0f, 2.0f);
m_jumpTime = game.time () + rg (1.0f, 3.0f);
}
static Array <edict_t *> players {};
players.clear ();
@ -3253,7 +3249,7 @@ void Bot::logicDuringFreezetime () {
m_needToSendWelcomeChat = false;
}
}
m_changeViewTime = game.time () + rg (1.25f, 2.0f);
m_changeViewTime = game.time () + rg (1.25f, 3.0f);
}
void Bot::executeTasks () {
@ -3389,6 +3385,9 @@ void Bot::logic () {
m_isUsingGrenade = false;
executeTasks (); // execute current task
setAimDirection (); // choose aim direction
updateLookAngles (); // and turn to chosen aim direction
doFireWeapons (); // fire the weapons
// check for reloading
if (m_reloadCheckTime <= game.time ()) {
@ -3471,14 +3470,6 @@ void Bot::logic () {
m_lastDamageType = -1; // reset damage
}
void Bot::upkeep () {
setAimDirection ();
doFireWeapons ();
updateLookAngles ();
runMovement ();
}
void Bot::spawned () {
if (game.is (GameFlags::CSDM | GameFlags::ZombieMod)) {
newRound ();

View file

@ -352,6 +352,7 @@ void BotConfig::loadChatterConfig () {
{ "Chatter_Camp", Chatter::Camping, 10.0f },
{ "Chatter_OnARoll", Chatter::OnARoll, kMaxChatterRepeatInterval},
};
Array <String> badFiles {};
while (file.getLine (line)) {
line.trim ();
@ -393,7 +394,7 @@ void BotConfig::loadChatterConfig () {
m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration);
}
else {
game.print ("Warning: Couldn't get duration of sound '%s.wav.", sound);
badFiles.push (sound);
}
}
sentences.clear ();
@ -402,6 +403,10 @@ void BotConfig::loadChatterConfig () {
}
}
file.close ();
if (!badFiles.empty ()) {
game.print ("Warning: Couldn't get duration of next chatter sounds: %s.", String::join (badFiles, ","));
}
}
else {
cv_radio_mode.set (1);

View file

@ -1742,7 +1742,7 @@ int BotControl::menuAutoPathDistance (int item) {
return BotCommandResult::Handled;
}
int BotControl::menuKickPage1 (int item) {
int BotControl::menuKickPage (int page, int item) {
closeMenu (); // reset menu display
switch (item) {
@ -1754,93 +1754,21 @@ int BotControl::menuKickPage1 (int item) {
case 6:
case 7:
case 8:
bots.kickBot (item - 1);
kickBotByMenu (1);
bots.kickBot (item + (page - 1) * 8 - 1);
kickBotByMenu (page);
break;
case 9:
kickBotByMenu (2);
kickBotByMenu (page + 1);
break;
case 10:
if (page == 1) {
showMenu (Menu::Control);
break;
}
return BotCommandResult::Handled;
}
int BotControl::menuKickPage2 (int item) {
closeMenu (); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item + 8 - 1);
kickBotByMenu (2);
break;
case 9:
kickBotByMenu (3);
break;
case 10:
kickBotByMenu (1);
break;
else {
kickBotByMenu (page - 1);
}
return BotCommandResult::Handled;
}
int BotControl::menuKickPage3 (int item) {
closeMenu (); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item + 16 - 1);
kickBotByMenu (3);
break;
case 9:
kickBotByMenu (4);
break;
case 10:
kickBotByMenu (2);
break;
}
return BotCommandResult::Handled;
}
int BotControl::menuKickPage4 (int item) {
closeMenu (); // reset menu display
switch (item) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
bots.kickBot (item + 24 - 1);
kickBotByMenu (4);
break;
case 10:
kickBotByMenu (3);
break;
}
return BotCommandResult::Handled;
@ -2229,7 +2157,7 @@ BotControl::BotControl () {
m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team] [silent]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots);
m_cmds.emplace ("fill/fillserver", "fill [team] [count] [difficulty] [personality]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill);
m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote);
m_cmds.emplace ("weapons/weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use", &BotControl::cmdWeaponMode);
m_cmds.emplace ("weapons/weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use.", &BotControl::cmdWeaponMode);
m_cmds.emplace ("menu/botmenu", "menu [cmd]", "Opens the main bot menu, or command menu if specified.", &BotControl::cmdMenu);
m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion);
m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph editor.", &BotControl::cmdNodeMenu);

View file

@ -338,7 +338,7 @@ Vector Game::getEntityOrigin (edict_t *ent) {
}
if (ent->v.origin.empty ()) {
return ent->v.absmin + (ent->v.size * 0.5);
return ent->v.absmin + ent->v.size * 0.5;
}
return ent->v.origin;
}
@ -935,7 +935,12 @@ bool Game::loadCSBinary () {
}
if (entity != nullptr) {
m_gameFlags |= (GameFlags::Modern | GameFlags::HasBotVoice | GameFlags::HasFakePings);
m_gameFlags |= (GameFlags::Modern | GameFlags::HasBotVoice);
// no fake pings on xash3d
if (!(m_gameFlags & (GameFlags::Xash3D | GameFlags::Xash3DLegacy))) {
m_gameFlags |= GameFlags::HasFakePings;
}
}
else {
m_gameFlags |= GameFlags::Legacy;

View file

@ -40,8 +40,6 @@ void BotFakePingManager::reset (edict_t *to) {
}
void BotFakePingManager::syncCalculate () {
MutexScopedLock lock (m_cs);
int averagePing {};
if (cv_ping_count_real_players) {

View file

@ -576,7 +576,7 @@ IntArray BotGraph::getNearestInRadius (const float radius, const Vector &origin,
if (bucket.length () < kMaxNodeLinks || radius > cr::sqrf (256.0f)) {
for (const auto &path : m_paths) {
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) {
if (maxCount != -1 && result.length <int32_t> () > maxCount) {
break;
}
@ -588,7 +588,7 @@ IntArray BotGraph::getNearestInRadius (const float radius, const Vector &origin,
}
for (const auto &at : bucket) {
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) {
if (maxCount != -1 && result.length <int32_t> () > maxCount) {
break;
}
@ -1292,6 +1292,7 @@ void BotGraph::showFileInfo () {
msg (" uncompressed_size: %dkB", info.uncompressed / 1024);
msg (" options: %d", info.options); // display as string ?
msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ?
msg (" pathfinder: %s", planner.isPathsCheckFailed () ? "floyd" : "astar");
msg ("");

View file

@ -1023,15 +1023,15 @@ CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
gpMetaUtilFuncs->pfnLogError (PLID, "%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
return HLFalse; // returning FALSE prevents metamod from unloading this plugin
}
// stop the worker
worker.shutdown ();
// kick all bots off this server
bots.kickEveryone (true);
// save collected practice on shutdown
practice.save ();
// stop the worker
worker.shutdown ();
// disable hooks
fakequeries.disable ();

View file

@ -348,6 +348,10 @@ void BotManager::maintainQuota () {
// this function keeps number of bots up to date, and don't allow to maintain bot creation
// while creation process in process.
if (!m_holdQuotaManagementTimer.elapsed ()) {
return;
}
if (graph.length () < 1 || graph.hasChanged ()) {
if (cv_quota.as <int> () > 0) {
ctrl.msg ("There is no graph found. Cannot create bot.");
@ -707,8 +711,8 @@ void BotManager::kickBot (int index) {
auto bot = findBotByIndex (index);
if (bot) {
decrementQuota ();
bot->kick ();
decrementQuota ();
}
}
@ -855,10 +859,15 @@ void BotManager::checkBotModel (edict_t *ent, char *infobuffer) {
}
void BotManager::checkNeedsToBeKicked () {
// this is called to check if bot is leaving server due to pathfinding error
// so this will cause to delay quota management stuff from executing for some time
for (const auto &bot : bots) {
if (bot->m_kickMeFromServer) {
m_holdQuotaManagementTimer.start (10.0f);
m_addRequests.clear ();
bot->kick (); // kick bot from server if requested
break;
}
}
}
@ -1118,6 +1127,8 @@ void BotManager::balanceBotDifficulties () {
void BotManager::destroy () {
// this function free all bots slots (used on server shutdown)
m_holdQuotaManagementTimer.invalidate (); // restart quota manager
for (auto &bot : m_bots) {
bot->markStale ();
}
@ -1739,15 +1750,12 @@ void Bot::newRound () {
thinkInterval = 1.0f / 50.0f; // xash3d works acceptable at 50fps
}
}
auto fullThinkInterval = 1.0f / (thinkFps * 0.5f);
// legacy games behaves strange, when this enabled, disable for xash3d as well if requested
if (bots.isFrameSkipDisabled ()) {
thinkInterval = 0.0f;
fullThinkInterval = 0.0f;
}
m_thinkTimer.interval = thinkInterval;
m_fullThinkTimer.interval = fullThinkInterval;
}
void Bot::resetPathSearchType () {
@ -1804,10 +1812,6 @@ void Bot::kick (bool silent) {
}
void Bot::markStale () {
// wait till threads tear down
MutexScopedLock lock1 (m_pathFindLock);
MutexScopedLock lock2 (m_predictLock);
// switch chatter icon off
showChatterIcon (false, true);
@ -1817,6 +1821,10 @@ void Bot::markStale () {
// mark bot as leaving
m_isStale = true;
// wait till threads tear down
MutexScopedLock lock1 (m_pathFindLock);
MutexScopedLock lock2 (m_predictLock);
// clear the bot name
conf.clearUsedName (this);
@ -2397,7 +2405,7 @@ bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius;
// return true if the total length of smoke-covered line-of-sight is too much
return (totalSmokedLength > maxSmokedLength);
return totalSmokedLength > maxSmokedLength;
}
bool BotManager::isFrameSkipDisabled () {

View file

@ -552,7 +552,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
if (graph.exists (destIndex)) {
findPath (m_currentNodeIndex, destIndex, other->m_pathType);
other->findPath (m_currentNodeIndex, destIndex);
other->findPath (m_currentNodeIndex, destIndex, m_pathType);
}
}
}
@ -646,13 +646,6 @@ void Bot::checkTerrain (const Vector &dirNormal) {
}
}
if (m_probeTime >= game.time ()) {
m_isStuck = true;
}
else if (m_probeTime + randomProbeTime < game.time () && !cr::fzero (m_probeTime)) {
resetCollision (); // resets its collision state because it was too long time in probing state
}
// not stuck?
if (!m_isStuck) {
if (m_probeTime + randomProbeTime < game.time ()) {
@ -914,6 +907,15 @@ void Bot::checkFall () {
return;
}
// take in account node sequences levels to compensate surface inclining
if (m_previousNodes[0] != kInvalidNodeIndex) {
const auto &pn = graph[m_previousNodes[0]];
if (cr::abs (pn.origin.z - m_pathOrigin.z) < cv_graph_analyze_max_jump_height.as <float> ()) {
return;
}
}
if (!m_checkFall) {
if (isOnFloor ()) {
m_checkFallPoint[0] = pev->origin;
@ -948,6 +950,7 @@ void Bot::checkFall () {
&& (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f)
&& baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
fixFall = true;
}
else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f
&& m_checkFallPoint[0].z > pev->origin.z + 128.0f) {
@ -1988,7 +1991,7 @@ float Bot::getEstimatedNodeReachTime () {
}
estimatedTime = cr::clamp (estimatedTime, 3.0f, longTermReachability ? 8.0f : 3.5f);
}
return estimatedTime;
return estimatedTime += m_lastDamageTimestamp >= game.time () ? 1.0f : 0.0f;
}
void Bot::findValidNode () {
@ -3148,10 +3151,7 @@ bool Bot::checkWallOnLeft (float distance) {
game.testLine (pev->origin, pev->origin - pev->angles.right () * distance, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f) {
return true;
}
return false;
return tr.flFraction < 1.0f;
}
bool Bot::checkWallOnRight (float distance) {
@ -3161,10 +3161,7 @@ bool Bot::checkWallOnRight (float distance) {
game.testLine (pev->origin, pev->origin + pev->angles.right () * distance, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f) {
return true;
}
return false;
return tr.flFraction < 1.0f;
}
bool Bot::checkWallOnBehind (float distance) {
@ -3174,10 +3171,7 @@ bool Bot::checkWallOnBehind (float distance) {
game.testLine (pev->origin, pev->origin - pev->angles.forward () * distance, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f) {
return true;
}
return false;
return tr.flFraction < 1.0f;
}
bool Bot::isDeadlyMove (const Vector &to) {
@ -3448,6 +3442,10 @@ bool Bot::isPreviousLadder () const {
void Bot::findShortestPath (int srcIndex, int destIndex) {
// this function finds the shortest path from source index to destination index
// stale bots shouldn't do pathfinding
if (m_isStale) {
return;
}
clearSearchNodes ();
m_chosenGoalIndex = srcIndex;
@ -3472,6 +3470,11 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
}
ScopedUnlock <Mutex> unlock (m_pathFindLock);
// stale bots shouldn't do pathfinding
if (m_isStale) {
return;
}
if (!graph.exists (srcIndex)) {
srcIndex = changeNodeIndex (graph.getNearestNoBuckets (pev->origin, 256.0f));

View file

@ -274,6 +274,11 @@ AStarResult AStarAlgo::find (int botTeam, int srcIndex, int destIndex, NodeAdder
// safes us from bad graph...
if (m_routeQue.length () >= getMaxLength () - 1) {
m_routeQue.clear ();
// infrom pathfinder to use floyds in that case
planner.setPathsCheckFailed (true);
return AStarResult::InternalError;
}

View file

@ -18,7 +18,6 @@ void BotPractice::setIndex (int32_t team, int32_t start, int32_t goal, int32_t v
if (team != Team::Terrorist && team != Team::CT) {
return;
}
MutexScopedLock lock (m_damageUpdateLock);
// reliability check
if (!graph.exists (start) || !graph.exists (goal) || !graph.exists (value)) {
@ -38,7 +37,6 @@ void BotPractice::setValue (int32_t team, int32_t start, int32_t goal, int32_t v
if (team != Team::Terrorist && team != Team::CT) {
return;
}
MutexScopedLock lock (m_damageUpdateLock);
// reliability check
if (!graph.exists (start) || !graph.exists (goal)) {
@ -58,7 +56,6 @@ void BotPractice::setDamage (int32_t team, int32_t start, int32_t goal, int32_t
if (team != Team::Terrorist && team != Team::CT) {
return;
}
MutexScopedLock lock (m_damageUpdateLock);
// reliability check
if (!graph.exists (start) || !graph.exists (goal)) {
@ -72,6 +69,7 @@ float BotPractice::plannerGetDamage (int32_t team, int32_t start, int32_t goal,
return 0.0f;
}
ScopedUnlock <Mutex> unlock (m_damageUpdateLock);
auto damage = static_cast <float> (getDamage (team, start, goal));
if (addTeamHighestDamage) {
@ -97,6 +95,8 @@ void BotPractice::syncUpdate () {
}
auto adjustValues = false;
MutexScopedLock lock (m_damageUpdateLock);
// get the most dangerous node for this position for both teams
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
auto bestIndex = kInvalidNodeIndex; // best index to store
@ -142,7 +142,6 @@ void BotPractice::syncUpdate () {
}
}
}
MutexScopedLock lock (m_damageUpdateLock);
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
m_teamHighestDamage[team] = cr::clamp (m_teamHighestDamage[team] - kHalfDamageVal, 1, kFullDamageVal);

View file

@ -329,46 +329,37 @@ void BotSupport::checkWelcome () {
}
}
bool BotSupport::findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool needAlive, bool needDrawn, bool needBotWithC4) {
bool BotSupport::findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot,
bool needAlive, bool needDrawn, bool needBotWithC4) {
// this function finds nearest to to, player with set of parameters, like his
// team, live status, search distance etc. if needBot is true, then pvHolder, will
// be filled with bot pointer, else with edict pointer(!).
searchDistance = cr::sqrf (searchDistance);
edict_t *survive = nullptr; // pointer to temporally & survive entity
float nearestPlayer = 4096.0f; // nearest player
const int toTeam = game.getTeam (to);
float nearestPlayerDistanceSq = cr::sqrf (4096.0f); // nearest player
for (const auto &client : m_clients) {
if (!(client.flags & ClientFlags::Used) || client.ent == to) {
continue;
}
if ((sameTeam && client.team != toTeam) || (needAlive && !(client.flags & ClientFlags::Alive)) || (needBot && !bots[client.ent]) || (needDrawn && (client.ent->v.effects & EF_NODRAW)) || (needBotWithC4 && (client.ent->v.weapons & Weapon::C4))) {
if ((sameTeam && client.team != game.getTeam (to))
|| (needAlive && !(client.flags & ClientFlags::Alive))
|| (needBot && !bots[client.ent])
|| (needDrawn && (client.ent->v.effects & EF_NODRAW))
|| (needBotWithC4 && (client.ent->v.weapons & Weapon::C4))) {
continue; // filter players with parameters
}
const float distanceSq = client.ent->v.origin.distanceSq (to->v.origin);
if (distanceSq < nearestPlayer && distanceSq < searchDistance) {
nearestPlayer = distanceSq;
survive = client.ent;
if (distanceSq < nearestPlayerDistanceSq && distanceSq < searchDistance) {
nearestPlayerDistanceSq = distanceSq;
*pvHolder = needBot ? reinterpret_cast <void *> (bots[client.ent]) : reinterpret_cast <void *> (client.ent);
}
}
if (game.isNullEntity (survive)) {
return false; // nothing found
}
// fill the holder
if (needBot) {
*pvHolder = reinterpret_cast <void *> (bots[survive]);
}
else {
*pvHolder = reinterpret_cast <void *> (survive);
}
return true;
return !!*pvHolder;
}
void BotSupport::updateClients () {

View file

@ -425,15 +425,24 @@ void Bot::setAimDirection () {
if (!(flags & (AimFlags::Grenade | AimFlags::Enemy | AimFlags::Entity))) {
// check if narrow place and we're duck, do not predict enemies in that situation
const bool duckedInNarrowPlace = isInNarrowPlace () && ((m_pathFlags & NodeFlag::Crouch) || (pev->button & IN_DUCK));
const bool duckedInNarrowPlace = isInNarrowPlace ()
&& ((m_pathFlags & NodeFlag::Crouch)
|| (pev->button & IN_DUCK));
if (duckedInNarrowPlace
|| isOnLadder ()
|| isInWater ()
|| (m_pathFlags & NodeFlag::Ladder)
|| (m_currentTravelFlags & PathFlag::Jump)) {
if (duckedInNarrowPlace || isOnLadder () || isInWater () || (m_pathFlags & NodeFlag::Ladder) || (m_currentTravelFlags & PathFlag::Jump)) {
flags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath);
m_canChooseAimDirection = false;
}
// don't switch view right away after loosing focus with current enemy
if ((m_shootTime + rg (1.0f, 1.5f) > game.time () || m_seeEnemyTime + 1.5f > game.time ())
if ((m_shootTime + rg (0.75f, 1.25f) > game.time ()
|| m_seeEnemyTime + 1.5f > game.time ())
&& m_forgetLastVictimTimer.elapsed ()
&& !m_lastEnemyOrigin.empty ()
&& util.isAlive (m_lastEnemy)
@ -619,7 +628,10 @@ void Bot::setAimDirection () {
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) {
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 + pev->view_ofs;
}
}
@ -630,7 +642,10 @@ void Bot::setAimDirection () {
}
// 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 ()) {
if (game.isNullEntity (m_enemy) && m_difficulty >= Difficulty::Normal
&& !m_forgetLastVictimTimer.elapsed ()
&& !m_lastVictimOrigin.empty ()) {
m_lookAt = m_lastVictimOrigin + pev->view_ofs;
}
}

View file

@ -324,7 +324,7 @@
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<DisableLanguageExtensions>false</DisableLanguageExtensions>
<LanguageStandard>stdcpp17</LanguageStandard>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>