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] [ORIGINAL]
Sets the bots weapon mode to use Sets the bots weapon mode to use.
[TRANSLATED] [TRANSLATED]
设置人机的武器模式 设置人机的武器模式
[ORIGINAL] [ORIGINAL]
Opens the main bot menu, or command menu if specified. 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. Zwingt alle Bots, für die angegebene Karte zu stimmen.
[ORIGINAL] [ORIGINAL]
Sets the bots weapon mode to use Sets the bots weapon mode to use.
[TRANSLATED] [TRANSLATED]
Legt den zu verwendenden Waffenmodus des Bots fest Legt den zu verwendenden Waffenmodus des Bots fest.
[ORIGINAL] [ORIGINAL]
Opens the main bot menu, or command menu if specified. Opens the main bot menu, or command menu if specified.

View file

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

View file

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

View file

@ -157,10 +157,23 @@ private:
int menuGraphPath (int item); int menuGraphPath (int item);
int menuCampDirections (int item); int menuCampDirections (int item);
int menuAutoPathDistance (int item); int menuAutoPathDistance (int item);
int menuKickPage1 (int item);
int menuKickPage2 (int item); int menuKickPage1 (int item) {
int menuKickPage3 (int item); return menuKickPage (1, item);
int menuKickPage4 (int 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: private:
void createMenus (); void createMenus ();

View file

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

View file

@ -59,6 +59,8 @@ private:
edict_t *m_killerEntity {}; // killer entity for bots edict_t *m_killerEntity {}; // killer entity for bots
BotTeamData m_teamData[kGameTeamNum] {}; // teams shared data BotTeamData m_teamData[kGameTeamNum] {}; // teams shared data
CountdownTimer m_holdQuotaManagementTimer {}; // prevent from running quota management for some time
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);

View file

@ -275,6 +275,10 @@ public:
return m_pathsCheckFailed; return m_pathsCheckFailed;
} }
void setPathsCheckFailed (const bool value) {
m_pathsCheckFailed = value;
}
public: public:
// do the pathfinding // do the pathfinding
bool find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance = nullptr); 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) { void setHighestDamageForTeam (int32_t team, int32_t value) {
MutexScopedLock lock (m_damageUpdateLock);
m_teamHighestDamage[team] = value; m_teamHighestDamage[team] = value;
} }
}; };

View file

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

View file

@ -730,7 +730,6 @@ public:
Array <int32_t> m_goalHist {}; Array <int32_t> m_goalHist {};
FrameDelay m_thinkTimer {}; FrameDelay m_thinkTimer {};
FrameDelay m_fullThinkTimer {};
public: public:
Bot (edict_t *bot, int difficulty, int personality, int team, int skin); Bot (edict_t *bot, int difficulty, int personality, int team, int skin);
@ -738,7 +737,6 @@ public:
public: public:
void logic (); /// the things that can be executed while skipping frames void logic (); /// the things that can be executed while skipping frames
void upkeep ();
void spawned (); void spawned ();
void takeBlind (int alpha); void takeBlind (int alpha);
void takeDamage (edict_t *inflictor, int damage, int armor, int bits); 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', '-fdata-sections',
'-ffunction-sections', '-ffunction-sections',
'-fcf-protection=none', '-fcf-protection=none',
'-fno-plt' '-fno-plt',
'-Wa,--noexecstack'
] ]
ldflags += [ ldflags += [
'-Wl,--version-script=' + meson.project_source_root() + '/ext/ldscripts/version.lds', '-Wl,--version-script=' + meson.project_source_root() + '/ext/ldscripts/version.lds',
'-Wl,--gc-sections' '-Wl,--gc-sections',
'-Wl,-z,noexecstack'
] ]
else else
cxxflags += ['-DLINKENT_STATIC'] cxxflags += ['-DLINKENT_STATIC']
@ -177,7 +179,7 @@ if cxx == 'clang' or cxx == 'gcc'
] ]
endif endif
else else
cxxflags += ['-g3', '-ggdb', '-DDEBUG', '-D_FORTIFY_SOURCE=2'] cxxflags += ['-g3', '-ggdb', '-DDEBUG']
endif endif
# special script for mingw-64 builds # special script for mingw-64 builds

View file

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

View file

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

View file

@ -1742,7 +1742,7 @@ int BotControl::menuAutoPathDistance (int item) {
return BotCommandResult::Handled; return BotCommandResult::Handled;
} }
int BotControl::menuKickPage1 (int item) { int BotControl::menuKickPage (int page, int item) {
closeMenu (); // reset menu display closeMenu (); // reset menu display
switch (item) { switch (item) {
@ -1754,93 +1754,21 @@ int BotControl::menuKickPage1 (int item) {
case 6: case 6:
case 7: case 7:
case 8: case 8:
bots.kickBot (item - 1); bots.kickBot (item + (page - 1) * 8 - 1);
kickBotByMenu (1); kickBotByMenu (page);
break; break;
case 9: case 9:
kickBotByMenu (2); kickBotByMenu (page + 1);
break; break;
case 10: case 10:
showMenu (Menu::Control); if (page == 1) {
break; showMenu (Menu::Control);
} }
return BotCommandResult::Handled; else {
} kickBotByMenu (page - 1);
}
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;
}
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; break;
} }
return BotCommandResult::Handled; 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 ("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 ("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 ("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 ("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 ("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); 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 ()) { 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; return ent->v.origin;
} }
@ -935,7 +935,12 @@ bool Game::loadCSBinary () {
} }
if (entity != nullptr) { 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 { else {
m_gameFlags |= GameFlags::Legacy; m_gameFlags |= GameFlags::Legacy;

View file

@ -40,8 +40,6 @@ void BotFakePingManager::reset (edict_t *to) {
} }
void BotFakePingManager::syncCalculate () { void BotFakePingManager::syncCalculate () {
MutexScopedLock lock (m_cs);
int averagePing {}; int averagePing {};
if (cv_ping_count_real_players) { 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)) { if (bucket.length () < kMaxNodeLinks || radius > cr::sqrf (256.0f)) {
for (const auto &path : m_paths) { for (const auto &path : m_paths) {
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) { if (maxCount != -1 && result.length <int32_t> () > maxCount) {
break; break;
} }
@ -588,7 +588,7 @@ IntArray BotGraph::getNearestInRadius (const float radius, const Vector &origin,
} }
for (const auto &at : bucket) { for (const auto &at : bucket) {
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) { if (maxCount != -1 && result.length <int32_t> () > maxCount) {
break; break;
} }
@ -1292,6 +1292,7 @@ void BotGraph::showFileInfo () {
msg (" uncompressed_size: %dkB", info.uncompressed / 1024); msg (" uncompressed_size: %dkB", info.uncompressed / 1024);
msg (" options: %d", info.options); // display as string ? msg (" options: %d", info.options); // display as string ?
msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ? msg (" analyzed: %s", isAnalyzed () ? conf.translate ("yes") : conf.translate ("no")); // display as string ?
msg (" pathfinder: %s", planner.isPathsCheckFailed () ? "floyd" : "astar");
msg (""); 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); 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 return HLFalse; // returning FALSE prevents metamod from unloading this plugin
} }
// stop the worker
worker.shutdown ();
// kick all bots off this server // kick all bots off this server
bots.kickEveryone (true); bots.kickEveryone (true);
// save collected practice on shutdown // save collected practice on shutdown
practice.save (); practice.save ();
// stop the worker
worker.shutdown ();
// disable hooks // disable hooks
fakequeries.disable (); 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 // this function keeps number of bots up to date, and don't allow to maintain bot creation
// while creation process in process. // while creation process in process.
if (!m_holdQuotaManagementTimer.elapsed ()) {
return;
}
if (graph.length () < 1 || graph.hasChanged ()) { if (graph.length () < 1 || graph.hasChanged ()) {
if (cv_quota.as <int> () > 0) { if (cv_quota.as <int> () > 0) {
ctrl.msg ("There is no graph found. Cannot create bot."); ctrl.msg ("There is no graph found. Cannot create bot.");
@ -707,8 +711,8 @@ void BotManager::kickBot (int index) {
auto bot = findBotByIndex (index); auto bot = findBotByIndex (index);
if (bot) { if (bot) {
decrementQuota ();
bot->kick (); bot->kick ();
decrementQuota ();
} }
} }
@ -855,10 +859,15 @@ void BotManager::checkBotModel (edict_t *ent, char *infobuffer) {
} }
void BotManager::checkNeedsToBeKicked () { 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) { for (const auto &bot : bots) {
if (bot->m_kickMeFromServer) { if (bot->m_kickMeFromServer) {
m_holdQuotaManagementTimer.start (10.0f);
m_addRequests.clear ();
bot->kick (); // kick bot from server if requested bot->kick (); // kick bot from server if requested
break;
} }
} }
} }
@ -1118,6 +1127,8 @@ void BotManager::balanceBotDifficulties () {
void BotManager::destroy () { void BotManager::destroy () {
// this function free all bots slots (used on server shutdown) // this function free all bots slots (used on server shutdown)
m_holdQuotaManagementTimer.invalidate (); // restart quota manager
for (auto &bot : m_bots) { for (auto &bot : m_bots) {
bot->markStale (); bot->markStale ();
} }
@ -1739,15 +1750,12 @@ void Bot::newRound () {
thinkInterval = 1.0f / 50.0f; // xash3d works acceptable at 50fps 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 // legacy games behaves strange, when this enabled, disable for xash3d as well if requested
if (bots.isFrameSkipDisabled ()) { if (bots.isFrameSkipDisabled ()) {
thinkInterval = 0.0f; thinkInterval = 0.0f;
fullThinkInterval = 0.0f;
} }
m_thinkTimer.interval = thinkInterval; m_thinkTimer.interval = thinkInterval;
m_fullThinkTimer.interval = fullThinkInterval;
} }
void Bot::resetPathSearchType () { void Bot::resetPathSearchType () {
@ -1804,10 +1812,6 @@ void Bot::kick (bool silent) {
} }
void Bot::markStale () { void Bot::markStale () {
// wait till threads tear down
MutexScopedLock lock1 (m_pathFindLock);
MutexScopedLock lock2 (m_predictLock);
// switch chatter icon off // switch chatter icon off
showChatterIcon (false, true); showChatterIcon (false, true);
@ -1817,6 +1821,10 @@ void Bot::markStale () {
// mark bot as leaving // mark bot as leaving
m_isStale = true; m_isStale = true;
// wait till threads tear down
MutexScopedLock lock1 (m_pathFindLock);
MutexScopedLock lock2 (m_predictLock);
// clear the bot name // clear the bot name
conf.clearUsedName (this); conf.clearUsedName (this);
@ -2397,7 +2405,7 @@ bool BotManager::isLineBlockedBySmoke (const Vector &from, const Vector &to) {
const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius; const float maxSmokedLength = 0.7f * kSmokeGrenadeRadius;
// return true if the total length of smoke-covered line-of-sight is too much // return true if the total length of smoke-covered line-of-sight is too much
return (totalSmokedLength > maxSmokedLength); return totalSmokedLength > maxSmokedLength;
} }
bool BotManager::isFrameSkipDisabled () { bool BotManager::isFrameSkipDisabled () {

View file

@ -552,7 +552,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
if (graph.exists (destIndex)) { if (graph.exists (destIndex)) {
findPath (m_currentNodeIndex, destIndex, other->m_pathType); 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? // not stuck?
if (!m_isStuck) { if (!m_isStuck) {
if (m_probeTime + randomProbeTime < game.time ()) { if (m_probeTime + randomProbeTime < game.time ()) {
@ -914,6 +907,15 @@ void Bot::checkFall () {
return; 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 (!m_checkFall) {
if (isOnFloor ()) { if (isOnFloor ()) {
m_checkFallPoint[0] = pev->origin; m_checkFallPoint[0] = pev->origin;
@ -948,6 +950,7 @@ void Bot::checkFall () {
&& (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f) && (nowDistanceSq > baseDistanceSq * 1.2f || nowDistanceSq > baseDistanceSq + 200.0f)
&& baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) { && baseDistanceSq >= cr::sqrf (80.0f) && nowDistanceSq >= cr::sqrf (100.0f)) {
fixFall = true; fixFall = true;
} }
else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f else if (m_checkFallPoint[1].z > pev->origin.z + 128.0f
&& m_checkFallPoint[0].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); 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 () { 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); game.testLine (pev->origin, pev->origin - pev->angles.right () * distance, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something... // check if the trace hit something...
if (tr.flFraction < 1.0f) { return tr.flFraction < 1.0f;
return true;
}
return false;
} }
bool Bot::checkWallOnRight (float distance) { 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); game.testLine (pev->origin, pev->origin + pev->angles.right () * distance, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something... // check if the trace hit something...
if (tr.flFraction < 1.0f) { return tr.flFraction < 1.0f;
return true;
}
return false;
} }
bool Bot::checkWallOnBehind (float distance) { 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); game.testLine (pev->origin, pev->origin - pev->angles.forward () * distance, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something... // check if the trace hit something...
if (tr.flFraction < 1.0f) { return tr.flFraction < 1.0f;
return true;
}
return false;
} }
bool Bot::isDeadlyMove (const Vector &to) { bool Bot::isDeadlyMove (const Vector &to) {
@ -3448,6 +3442,10 @@ bool Bot::isPreviousLadder () const {
void Bot::findShortestPath (int srcIndex, int destIndex) { void Bot::findShortestPath (int srcIndex, int destIndex) {
// this function finds the shortest path from source index to destination index // this function finds the shortest path from source index to destination index
// stale bots shouldn't do pathfinding
if (m_isStale) {
return;
}
clearSearchNodes (); clearSearchNodes ();
m_chosenGoalIndex = srcIndex; m_chosenGoalIndex = srcIndex;
@ -3472,6 +3470,11 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
} }
ScopedUnlock <Mutex> unlock (m_pathFindLock); ScopedUnlock <Mutex> unlock (m_pathFindLock);
// stale bots shouldn't do pathfinding
if (m_isStale) {
return;
}
if (!graph.exists (srcIndex)) { if (!graph.exists (srcIndex)) {
srcIndex = changeNodeIndex (graph.getNearestNoBuckets (pev->origin, 256.0f)); 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... // safes us from bad graph...
if (m_routeQue.length () >= getMaxLength () - 1) { if (m_routeQue.length () >= getMaxLength () - 1) {
m_routeQue.clear ();
// infrom pathfinder to use floyds in that case
planner.setPathsCheckFailed (true);
return AStarResult::InternalError; 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) { if (team != Team::Terrorist && team != Team::CT) {
return; return;
} }
MutexScopedLock lock (m_damageUpdateLock);
// reliability check // reliability check
if (!graph.exists (start) || !graph.exists (goal) || !graph.exists (value)) { 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) { if (team != Team::Terrorist && team != Team::CT) {
return; return;
} }
MutexScopedLock lock (m_damageUpdateLock);
// reliability check // reliability check
if (!graph.exists (start) || !graph.exists (goal)) { 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) { if (team != Team::Terrorist && team != Team::CT) {
return; return;
} }
MutexScopedLock lock (m_damageUpdateLock);
// reliability check // reliability check
if (!graph.exists (start) || !graph.exists (goal)) { 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; return 0.0f;
} }
ScopedUnlock <Mutex> unlock (m_damageUpdateLock); ScopedUnlock <Mutex> unlock (m_damageUpdateLock);
auto damage = static_cast <float> (getDamage (team, start, goal)); auto damage = static_cast <float> (getDamage (team, start, goal));
if (addTeamHighestDamage) { if (addTeamHighestDamage) {
@ -97,6 +95,8 @@ void BotPractice::syncUpdate () {
} }
auto adjustValues = false; auto adjustValues = false;
MutexScopedLock lock (m_damageUpdateLock);
// get the most dangerous node for this position for both teams // get the most dangerous node for this position for both teams
for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
auto bestIndex = kInvalidNodeIndex; // best index to store 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) { for (int team = Team::Terrorist; team < kGameTeamNum; ++team) {
m_teamHighestDamage[team] = cr::clamp (m_teamHighestDamage[team] - kHalfDamageVal, 1, kFullDamageVal); 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 // 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 // team, live status, search distance etc. if needBot is true, then pvHolder, will
// be filled with bot pointer, else with edict pointer(!). // be filled with bot pointer, else with edict pointer(!).
searchDistance = cr::sqrf (searchDistance); searchDistance = cr::sqrf (searchDistance);
float nearestPlayerDistanceSq = cr::sqrf (4096.0f); // nearest player
edict_t *survive = nullptr; // pointer to temporally & survive entity
float nearestPlayer = 4096.0f; // nearest player
const int toTeam = game.getTeam (to);
for (const auto &client : m_clients) { for (const auto &client : m_clients) {
if (!(client.flags & ClientFlags::Used) || client.ent == to) { if (!(client.flags & ClientFlags::Used) || client.ent == to) {
continue; 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 continue; // filter players with parameters
} }
const float distanceSq = client.ent->v.origin.distanceSq (to->v.origin); const float distanceSq = client.ent->v.origin.distanceSq (to->v.origin);
if (distanceSq < nearestPlayer && distanceSq < searchDistance) { if (distanceSq < nearestPlayerDistanceSq && distanceSq < searchDistance) {
nearestPlayer = distanceSq; nearestPlayerDistanceSq = distanceSq;
survive = client.ent; *pvHolder = needBot ? reinterpret_cast <void *> (bots[client.ent]) : reinterpret_cast <void *> (client.ent);
} }
} }
return !!*pvHolder;
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;
} }
void BotSupport::updateClients () { void BotSupport::updateClients () {

View file

@ -425,15 +425,24 @@ void Bot::setAimDirection () {
if (!(flags & (AimFlags::Grenade | AimFlags::Enemy | AimFlags::Entity))) { if (!(flags & (AimFlags::Grenade | AimFlags::Enemy | AimFlags::Entity))) {
// check if narrow place and we're duck, do not predict enemies in that situation // 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); flags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath);
m_canChooseAimDirection = false; m_canChooseAimDirection = false;
} }
// don't switch view right away after loosing focus with current enemy // 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_forgetLastVictimTimer.elapsed ()
&& !m_lastEnemyOrigin.empty () && !m_lastEnemyOrigin.empty ()
&& util.isAlive (m_lastEnemy) && util.isAlive (m_lastEnemy)
@ -619,7 +628,10 @@ void Bot::setAimDirection () {
if (horizontalMovement && m_pathWalk.hasNext ()) { if (horizontalMovement && m_pathWalk.hasNext ()) {
const auto &nextPath = graph[m_pathWalk.next ()]; 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; 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 // 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; m_lookAt = m_lastVictimOrigin + pev->view_ofs;
} }
} }

View file

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