bot: refactor and clean up old code

fix: crash when saving old format pwf on hlds
bot: moved sdk headers to separate submodule
nav: improved unstuck and avoidance (thanks @commandcobra7) code
bot: use correct path slashes depending on platform for all data
cfg: removed simplified chines' translation, as it's too outdated
This commit is contained in:
jeefo 2023-05-12 20:00:06 +03:00
commit 7b58d51973
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
42 changed files with 365 additions and 3805 deletions

View file

@ -765,6 +765,10 @@ void Bot::instantChatter (int type) {
MessageWriter msg;
int ownIndex = index ();
auto writeChatterSound = [&msg] (ChatterItem item) {
msg.writeString (strings.format ("%s%s%s.wav", cv_chatter_path.str (), PATH_SEP, item.name));
};
for (auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) {
continue;
@ -774,11 +778,11 @@ void Bot::instantChatter (int type) {
if (pev->deadflag & DEAD_DYING) {
client.iconTimestamp[ownIndex] = game.time () + painSound.duration;
msg.writeString (strings.format ("%s/%s.wav", cv_chatter_path.str (), painSound.name));
writeChatterSound (painSound);
}
else if (!(pev->deadflag & DEAD_DEAD)) {
else if (m_notKilled) {
client.iconTimestamp[ownIndex] = game.time () + playbackSound.duration;
msg.writeString (strings.format ("%s/%s.wav", cv_chatter_path.str (), playbackSound.name));
writeChatterSound (playbackSound);
}
msg.writeShort (m_voicePitch).end ();
client.iconFlags[ownIndex] |= ClientFlags::Icon;
@ -1527,6 +1531,15 @@ void Bot::overrideConditions () {
// special handling for reloading
if (!bots.isRoundOver () && getCurrentTaskId () == Task::Normal && m_reloadState != Reload::None && m_isReloading && !isDucking () && !isInNarrowPlace ()) {
if (m_reloadState != Reload::None || m_isReloading) {
const auto maxClip = conf.findWeaponById (m_currentWeapon).maxClip;
const auto curClip = getAmmoInClip ();
// consider not reloading if full ammo in clip
if (curClip >= maxClip) {
m_isReloading = false;
}
}
if (m_seeEnemyTime + 2.5f < game.time () && (m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) {
m_moveSpeed = m_fearLevel > m_agressionLevel ? 0.0f : getShiftSpeed ();
m_navTimeset = game.time ();
@ -1550,7 +1563,7 @@ void Bot::syncUpdatePredictedIndex () {
auto currentNodeIndex = m_currentNodeIndex;
auto botOrigin = pev->origin;
if (lastEnemyOrigin.empty () || !vistab.isReady ()) {
if (lastEnemyOrigin.empty () || !vistab.isReady () || !util.isAlive (m_enemy)) {
wipePredict ();
return;
}
@ -1583,6 +1596,9 @@ void Bot::syncUpdatePredictedIndex () {
}
void Bot::updatePredictedIndex () {
if (m_lastEnemyOrigin.empty ()) {
return; // do not run task if no last enemy
}
worker.enqueue ([this] () {
syncUpdatePredictedIndex ();
});
@ -2651,11 +2667,6 @@ void Bot::update () {
kick ();
return;
}
pev->button = 0;
m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
m_moveAngles = nullptr;
m_canChooseAimDirection = true;
m_notKilled = util.isAlive (ent ());
@ -2714,6 +2725,9 @@ void Bot::update () {
else if (pev->maxspeed < 10.0f) {
logicDuringFreezetime ();
}
else if (!botMovement) {
resetMovement ();
}
runMovement ();
// delay next execution
@ -2826,6 +2840,8 @@ void Bot::logic () {
float movedDistance = 2.0f; // length of different vector (distance bot moved)
resetMovement ();
// increase reaction time
m_actualReactionTime += 0.3f;
@ -2977,6 +2993,7 @@ void Bot::logic () {
// save the previous speed (for checking if stuck)
m_prevSpeed = cr::abs (m_moveSpeed);
m_prevVelocity = cr::abs (pev->velocity.length2d ());
m_lastDamageType = -1; // reset damage
}
@ -3448,7 +3465,7 @@ Vector Bot::isBombAudible () {
}
// we hear bomb if length greater than radius
if (desiredRadius < pev->origin.distance2d (bombOrigin)) {
if (cr::sqrf (desiredRadius) < pev->origin.distanceSq2d (bombOrigin)) {
return bombOrigin;
}
return nullptr;
@ -3457,7 +3474,7 @@ Vector Bot::isBombAudible () {
uint8_t Bot::computeMsec () {
// estimate msec to use for this command based on time passed from the previous command
return static_cast <uint8_t> ((game.time () - m_lastCommandTime) * 1000.0f);
return static_cast <uint8_t> (cr::min (static_cast <int32_t> ((game.time () - m_lastCommandTime) * 1000.0f), 255));
}
bool Bot::canRunHeavyWeight () {
@ -3471,20 +3488,6 @@ bool Bot::canRunHeavyWeight () {
return false;
}
bool Bot::canSkipNextTrace (TraceChannel channel) {
// for optmization purposes skip every second traceline fired, this doesn't affect ai
// behaviour too much, but saves alot of cpu cycles.
constexpr auto kSkipTrace = 3;
// check if we're have to skip
if ((++m_traceSkip[channel] % kSkipTrace) == 0) {
m_traceSkip[channel] = 0;
return true;
}
return false;
}
void Bot::runMovement () {
// the purpose of this function is to compute, according to the specified computation
// method, the msec value which will be passed as an argument of pfnRunPlayerMove. This
@ -3761,7 +3764,7 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) {
}
float Bot::getShiftSpeed () {
if (getCurrentTaskId () == Task::SeekCover || isDucking () || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK) || (m_currentTravelFlags & PathFlag::Jump) || (m_path != nullptr && m_pathFlags & NodeFlag::Ladder) || isOnLadder () || isInWater () || m_isStuck) {
if (getCurrentTaskId () == Task::SeekCover || isDucking () || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK) || (m_currentTravelFlags & PathFlag::Jump) || (m_pathFlags & NodeFlag::Ladder) || isOnLadder () || isInWater () || m_isStuck) {
return pev->maxspeed;
}
return pev->maxspeed * 0.4f;

View file

@ -169,7 +169,7 @@ bool Bot::checkBodyParts (edict_t *target) {
else {
spot.z = target->v.origin.z - standFeet;
}
game.testLineChannel (TraceChannel::Enemy, eyes, spot, ignoreFlags, self, result);
game.testLine (eyes, spot, ignoreFlags, self, &result);
if (hitsTarget ()) {
m_enemyParts |= Visibility::Other;
@ -184,7 +184,7 @@ bool Bot::checkBodyParts (edict_t *target) {
Vector perp (-dir.y, dir.x, 0.0f);
spot = target->v.origin + Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, ignoreFlags, self, result);
game.testLine (eyes, spot, ignoreFlags, self, &result);
if (hitsTarget ()) {
m_enemyParts |= Visibility::Other;
@ -194,7 +194,7 @@ bool Bot::checkBodyParts (edict_t *target) {
}
spot = target->v.origin - Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, ignoreFlags, self, result);
game.testLine (eyes, spot, ignoreFlags, self, &result);
if (hitsTarget ()) {
m_enemyParts |= Visibility::Other;
@ -719,12 +719,9 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) {
bool Bot::isPenetrableObstacle3 (const Vector &dest) {
// this function returns if enemy can be shoot through some obstacle
if (m_isUsingGrenade || m_difficulty < Difficulty::Normal || !conf.findWeaponById (m_currentWeapon).penetratePower) {
return false;
}
auto power = conf.findWeaponById (m_currentWeapon).penetratePower;
if (power == 0) {
if (m_isUsingGrenade || m_difficulty < Difficulty::Normal || !power) {
return false;
}
TraceResult tr {};
@ -1218,7 +1215,7 @@ void Bot::attackMovement () {
};
auto strafeUpdateTime = [] () {
return game.time () + rg.get (0.5f, 1.0f);
return game.time () + rg.get (1.0f, 1.5f);
};
// to start strafing, we have to first figure out if the target is on the left side or right side
@ -1239,11 +1236,14 @@ void Bot::attackMovement () {
m_strafeSetTime = strafeUpdateTime ();
}
const bool wallOnRight = checkWallOnRight ();
const bool wallOnLeft = checkWallOnLeft ();
if (m_combatStrafeDir == Dodge::Right) {
if (!checkWallOnLeft ()) {
if (!wallOnLeft) {
m_strafeSpeed = -pev->maxspeed;
}
else if (!checkWallOnRight ()) {
else if (!wallOnRight) {
swapStrafeCombatDir ();
m_strafeSetTime = strafeUpdateTime ();
@ -1255,10 +1255,10 @@ void Bot::attackMovement () {
}
}
else {
if (!checkWallOnRight ()) {
if (!wallOnRight) {
m_strafeSpeed = pev->maxspeed;
}
else if (!checkWallOnLeft ()) {
else if (!wallOnLeft) {
swapStrafeCombatDir ();
m_strafeSetTime = strafeUpdateTime ();

View file

@ -48,8 +48,8 @@ void BotConfig::loadMainConfig (bool isFirstLoad) {
String line;
MemFile file;
// this is does the same as exec of engine, but not overwriting values of cvars spcified in cv_ignore_cvars_on_changelevel
if (util.openConfig (strings.format ("%s.cfg", product.folder), "Bot main config file is not found.", &file, false)) {
// this is does the same as exec of engine, but not overwriting values of cvars specified in cv_ignore_cvars_on_changelevel
if (util.openConfig (product.nameLower, "Bot main config file is not found.", &file, false)) {
while (file.getLine (line)) {
line.trim ();
@ -117,7 +117,7 @@ void BotConfig::loadNamesConfig () {
MemFile file;
// naming initialization
if (util.openConfig ("names.cfg", "Name configuration file not found.", &file, true)) {
if (util.openConfig ("names", "Name configuration file not found.", &file, true)) {
m_botNames.clear ();
while (file.getLine (line)) {
@ -172,7 +172,7 @@ void BotConfig::loadWeaponsConfig () {
MemFile file;
// weapon data initialization
if (util.openConfig ("weapon.cfg", "Weapon configuration file not found. Loading defaults.", &file)) {
if (util.openConfig ("weapon", "Weapon configuration file not found. Loading defaults.", &file)) {
while (file.getLine (line)) {
line.trim ();
@ -224,7 +224,7 @@ void BotConfig::loadChatterConfig () {
MemFile file;
// chatter initialization
if (game.is (GameFlags::HasBotVoice) && cv_radio_mode.int_ () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &file)) {
if (game.is (GameFlags::HasBotVoice) && cv_radio_mode.int_ () == 2 && util.openConfig ("chatter", "Couldn't open chatter system configuration", &file)) {
m_chatter.clear ();
struct EventMap {
@ -332,7 +332,7 @@ void BotConfig::loadChatterConfig () {
items[1].trim ("(;)");
for (const auto &event : chatterEventMap) {
if (event.str == items[0]) {
if (event.str == items.first ()) {
// this does common work of parsing comma-separated chatter line
auto sentences = items[1].split (",");
@ -364,7 +364,7 @@ void BotConfig::loadChatConfig () {
MemFile file;
// chat config initialization
if (util.openConfig ("chat.cfg", "Chat file not found.", &file, true)) {
if (util.openConfig ("chat", "Chat file not found.", &file, true)) {
StringArray *chat = nullptr;
StringArray keywords {};
@ -467,7 +467,7 @@ void BotConfig::loadLanguageConfig () {
MemFile file;
// localizer inititalization
if (util.openConfig ("lang.cfg", "Specified language not found.", &file, true)) {
if (util.openConfig ("lang", "Specified language not found.", &file, true)) {
String temp;
Twin <String, String> lang;
@ -513,7 +513,7 @@ void BotConfig::loadAvatarsConfig () {
MemFile file;
// avatars inititalization
if (util.openConfig ("avatars.cfg", "Avatars config file not found. Avatars will not be displayed.", &file)) {
if (util.openConfig ("avatars", "Avatars config file not found. Avatars will not be displayed.", &file)) {
m_avatars.clear ();
while (file.getLine (line)) {
@ -577,7 +577,7 @@ void BotConfig::loadDifficultyConfig () {
};
// avatars inititalization
if (util.openConfig ("difficulty.cfg", "Difficulty config file not found. Loading defaults.", &file)) {
if (util.openConfig ("difficulty", "Difficulty config file not found. Loading defaults.", &file)) {
while (file.getLine (line)) {
if (isCommentLine (line) || line.length () < 3) {
@ -612,10 +612,10 @@ void BotConfig::loadDifficultyConfig () {
}
void BotConfig::loadMapSpecificConfig () {
auto mapSpecificConfig = strings.format ("addons/%s/conf/maps/%s.cfg", product.folder, game.getMapName ());
auto mapSpecificConfig = strings.joinPath (folders.addons, folders.bot, folders.config, "maps", strings.format ("%s.%s", game.getMapName (), kConfigExtension));
// check existence of file
if (File::exists (strings.format ("%s/%s", game.getRunningModName (), mapSpecificConfig))) {
if (plat.fileExists (strings.joinPath (game.getRunningModName (), mapSpecificConfig).chars ())) {
game.serverCommand ("exec %s", mapSpecificConfig);
ctrl.msg ("Executed map-specific config: %s", mapSpecificConfig);
@ -630,7 +630,7 @@ void BotConfig::loadCustomConfig () {
m_custom["AMXParachuteCvar"] = "sv_parachute";
// custom inititalization
if (util.openConfig ("custom.cfg", "Custom config file not found. Loading defaults.", &file)) {
if (util.openConfig ("custom", "Custom config file not found. Loading defaults.", &file)) {
m_custom.clear ();
while (file.getLine (line)) {
@ -642,7 +642,7 @@ void BotConfig::loadCustomConfig () {
auto values = line.split ("=");
if (values.length () != 2) {
logger.error ("Bad configuration for custom.cfg");
logger.error ("Bad configuration for custom.%s", kConfigExtension);
return;
}
auto kv = Twin <String, String> (values[0].trim (), values[1].trim ());
@ -661,7 +661,7 @@ void BotConfig::loadLogosConfig () {
MemFile file;
// logos inititalization
if (util.openConfig ("logos.cfg", "Logos config file not found. Loading defaults.", &file)) {
if (util.openConfig ("logos", "Logos config file not found. Loading defaults.", &file)) {
m_logos.clear ();
while (file.getLine (line)) {
@ -688,7 +688,7 @@ void BotConfig::setupMemoryFiles () {
};
if (setMemoryPointers) {
MemFileStorage::instance ().initizalize (wrapLoadFile, wrapFreeFile);
MemFileStorage::instance ().initizalize (cr::move (wrapLoadFile), cr::move (wrapFreeFile));
setMemoryPointers = false;
}
}

View file

@ -201,9 +201,9 @@ int BotControl::cmdCvars () {
File cfg;
// if save requested, dump cvars to yapb.cfg
// if save requested, dump cvars to main config
if (isSave) {
cfg.open (strings.format ("%s/addons/%s/conf/%s.cfg", game.getRunningModName (), product.folder, product.folder), "wt");
cfg.open (strings.joinPath (game.getRunningModName (), folders.addons, folders.bot, folders.config, strings.format ("%s.%s", product.nameLower, kConfigExtension)), "wt");
cfg.puts ("// Configuration file for %s\n\n", product.name);
}
else {
@ -496,7 +496,7 @@ int BotControl::cmdNodeSave () {
msg ("All nodes has been saved and written to disk (IGNORING QUALITY CONTROL).");
}
else if (strValue (option) == "old") {
else if (strValue (option) == "old" || strValue (option) == "oldformat") {
if (graph.length () >= 1024) {
msg ("Unable to save POD-Bot Format waypoint file. Number of nodes exceeds 1024.");

View file

@ -250,35 +250,6 @@ void Game::testLine (const Vector &start, const Vector &end, int ignoreFlags, ed
engfuncs.pfnTraceLine (start, end, engineFlags, ignoreEntity, ptr);
}
bool Game::testLineChannel (TraceChannel channel, const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult &result) {
// this function traces a line dot by dot, starting from vecStart in the direction of vecEnd,
// ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops
// at the first obstacle encountered, returning the results of the trace in the TraceResult structure
// ptr. Such results are (amongst others) the distance traced, the hit surface, the hit plane
// vector normal, etc. See the TraceResult structure for details. This function allows to specify
// whether the trace starts "inside" an entity's polygonal model, and if so, to specify that entity
// in ignoreEntity in order to ignore it as a possible obstacle.
auto bot = bots[ignoreEntity];
// check if bot is firing trace line
if (bot && bot->canSkipNextTrace (channel)) {
result = bot->getLastTraceResult (channel); // set the result from bot stored one
// current call is skipped
return true;
}
else {
testLine (start, end, ignoreFlags, ignoreEntity, &result);
// if we're still reaching here, save the last trace result
if (bot) {
bot->setLastTraceResult (channel, &result);
}
}
return false;
}
void Game::testHull (const Vector &start, const Vector &end, int ignoreFlags, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr) {
// this function traces a hull dot by dot, starting from vecStart in the direction of vecEnd,
// ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or
@ -295,7 +266,7 @@ void Game::testHull (const Vector &start, const Vector &end, int ignoreFlags, in
}
// helper class for reading wave header
class WaveEndianessHelper : public DenyCopying {
class WaveEndianessHelper : public NonCopyable {
private:
#if defined (CR_ARCH_CPU_BIG_ENDIAN)
bool little { false };
@ -321,7 +292,7 @@ public:
};
float Game::getWaveLen (const char *fileName) {
auto filePath = strings.format ("%s/%s.wav", cv_chatter_path.str (), fileName);
auto filePath = strings.joinPath (cv_chatter_path.str (), strings.format ("%s.wav", fileName));
MemFile fp (filePath);
@ -666,7 +637,7 @@ void Game::addNewCvar (const char *name, const char *value, const char *info, bo
reg.initial = static_cast <float> (atof (value));
}
auto eflags = FCVAR_EXTDLL;
int eflags = FCVAR_EXTDLL;
if (varType == Var::Normal) {
eflags |= FCVAR_SERVER;
@ -804,10 +775,10 @@ bool Game::loadCSBinary () {
// search the libraries inside game dlls directory
for (const auto &lib : libs) {
auto path = strings.format ("%s/dlls/%s.%s", modname, lib, DLL_SUFFIX);
auto path = strings.joinPath (modname, "dlls", lib + DLL_SUFFIX);
// if we can't read file, skip it
if (!File::exists (path)) {
if (!plat.fileExists (path.chars ())) {
continue;
}
@ -871,10 +842,16 @@ bool Game::postload () {
game.print (msg);
});
auto ensureBotPathExists = [this] (StringRef dir1, StringRef dir2) {
File::makePath (strings.joinPath (getRunningModName (), folders.addons, folders.bot, dir1, dir2).chars ());
};
// ensure we're have all needed directories
for (const auto &dir : StringArray { "conf/lang", "data/train", "data/graph", "data/logs", "data/pwf" }) {
File::createPath (strings.format ("%s/addons/%s/%s", getRunningModName (), product.folder, dir));
}
ensureBotPathExists (folders.config, folders.lang);
ensureBotPathExists (folders.data, folders.train);
ensureBotPathExists (folders.data, folders.graph);
ensureBotPathExists (folders.data, folders.logs);
ensureBotPathExists (folders.data, folders.podbot);
// set out user agent for http stuff
http.setUserAgent (strings.format ("%s/%s", product.name, product.version));
@ -1250,7 +1227,7 @@ template <typename S, typename M> bool LightMeasure::recursiveLightPoint (const
auto lightmap = surf->samples + dt * smax + ds;
// compute the lightmap color at a particular point
for (int maps = 0u; maps < MAXLIGHTMAPS && surf->styles[maps] != 255u; ++maps) {
for (int maps = 0u; maps < MAX_LIGHTMAPS && surf->styles[maps] != 255u; ++maps) {
uint32_t scale = m_lightstyleValue[surf->styles[maps]];
m_point.red += lightmap->r * scale;

View file

@ -9,7 +9,7 @@
// on other than win32/linux platforms i.e. arm we're using xash3d engine to run which exposes
// nice interface to handle with linkents. if ever rehlds or hlds engine will ever run on ARM or
// other platforms, and you wan't to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS
// other platforms, and you want to run bot on it without metamod, consider enabling LINKENT_STATIC_THUNKS
// when compiling the bot, to get it supported.
#if defined(LINKENT_STATIC_THUNKS)
void forwardEntity_helper (EntityFunction &addr, const char *name, entvars_t *pev) {

View file

@ -1648,8 +1648,20 @@ bool BotGraph::saveGraphData () {
void BotGraph::saveOldFormat () {
PODGraphHeader header {};
String editorName;
if (game.isNullEntity (m_editor) && !m_graphAuthor.empty ()) {
editorName = m_graphAuthor;
}
else if (!game.isNullEntity (m_editor)) {
editorName = m_editor->v.netname.chars ();
}
else {
editorName = product.name;
}
strings.copy (header.header, kPodbotMagic, sizeof (kPodbotMagic));
strings.copy (header.author, m_editor->v.netname.chars (), cr::bufsize (header.author));
strings.copy (header.author, editorName.chars (), cr::bufsize (header.author));
strings.copy (header.mapName, game.getMapName (), cr::bufsize (header.mapName));
header.mapName[31] = 0;

View file

@ -32,54 +32,6 @@ plugin_info_t Plugin_info = {
PT_ANYTIME, // when unloadable
};
void hook_ClientCommand (edict_t *ent, char const *format, ...) {
// this function forces the client whose player entity is ent to issue a client command.
// How it works is that clients all have a argv global string in their client DLL that
// stores the command string; if ever that string is filled with characters, the client DLL
// sends it to the engine as a command to be executed. When the engine has executed that
// command, this argv string is reset to zero. Here is somehow a curious implementation of
// ClientCommand: the engine sets the command it wants the client to issue in his argv, then
// the client DLL sends it back to the engine, the engine receives it then executes the
// command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have
// no client DLL, be certain never to call this function upon a bot entity, else it will just
// make the server crash. Since hordes of uncautious, not to say stupid, programmers don't
// even imagine some players on their servers could be bots, this check is performed less than
// sometimes actually by their side, that's why we strongly recommend to check it here too. In
// case it's a bot asking for a client command, we handle it like we do for bot commands
if (game.isNullEntity (ent)) {
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_SUPERCEDE);
}
return;
}
va_list ap;
auto buffer = strings.chars ();
va_start (ap, format);
vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap);
va_end (ap);
if (util.isFakeClient (ent) || (ent->v.flags & FL_DORMANT)) {
auto bot = bots[ent];
if (bot) {
bot->issueCommand (buffer);
}
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands
}
return;
}
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);
}
engfuncs.pfnClientCommand (ent, buffer);
}
CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
// this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or
// what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can
@ -496,7 +448,7 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
}
if (ents.needsBypass () && !game.is (GameFlags::Metamod)) {
table->pfnCreateNamedEntity = [] (int classname) -> edict_t *{
table->pfnCreateNamedEntity = [] (string_t classname) -> edict_t *{
if (ents.isPaused ()) {
ents.enable ();
@ -764,9 +716,6 @@ CR_LINKAGE_C int GetEngineFunctions (enginefuncs_t *table, int *) {
}
engfuncs.pfnSetClientMaxspeed (ent, newMaxspeed);
};
table->pfnClientCommand = hook_ClientCommand;
return HLTrue;
}
@ -946,6 +895,11 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *table, globalvars_t *glob) {
memcpy (&engfuncs, table, sizeof (enginefuncs_t));
globals = glob;
// set the global timer function
timerStorage.setTimeFunction ([] () {
return globals->time;
});
if (game.postload ()) {
return;
}

View file

@ -93,7 +93,7 @@ BotManager::BotManager () {
void BotManager::createKillerEntity () {
// this function creates single trigger_hurt for using in Bot::kill, to reduce lags, when killing all the bots
m_killerEntity = engfuncs.pfnCreateNamedEntity (MAKE_STRING ("trigger_hurt"));
m_killerEntity = engfuncs.pfnCreateNamedEntity ("trigger_hurt");
m_killerEntity->v.dmg = kInfiniteDistance;
m_killerEntity->v.dmg_take = 1.0f;
@ -128,12 +128,12 @@ void BotManager::touchKillerEntity (Bot *bot) {
}
const auto &prop = conf.getWeaponProp (bot->m_currentWeapon);
m_killerEntity->v.classname = MAKE_STRING (prop.classname.chars ());
m_killerEntity->v.classname = prop.classname.chars ();
m_killerEntity->v.dmg_inflictor = bot->ent ();
m_killerEntity->v.dmg = (bot->pev->health + bot->pev->armorvalue) * 4.0f;
KeyValueData kv {};
kv.szClassName = const_cast <char *> (prop.classname.chars ());
kv.szClassName = prop.classname.chars ();
kv.szKeyName = "damagetype";
kv.szValue = strings.format ("%d", cr::bit (4));
kv.fHandled = HLFalse;
@ -146,7 +146,7 @@ void BotManager::execGameEntity (edict_t *ent) {
// this function calls gamedll player() function, in case to create player entity in game
if (game.is (GameFlags::Metamod)) {
CALL_GAME_ENTITY (PLID, "player", &ent->v);
MUTIL_CallGameEntity (PLID, "player", &ent->v);
return;
}
ents.callPlayerFunction (ent);
@ -214,7 +214,7 @@ BotCreateResult BotManager::create (StringRef name, int difficulty, int personal
resultName = botName->name;
}
else {
resultName.assignf ("%s_%d.%d", product.folder, rg.get (100, 10000), rg.get (100, 10000)); // just pick ugly random name
resultName.assignf ("%s_%d.%d", product.nameLower, rg.get (100, 10000), rg.get (100, 10000)); // just pick ugly random name
}
}
else {
@ -793,8 +793,25 @@ void BotManager::listBots () {
ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s\t%-3.5s\t%-3.8s", "index", "name", "personality", "team", "difficulty", "frags", "alive", "timeleft");
auto botTeam = [] (int32_t team) -> String {
switch (team) {
case Team::CT:
return "CT";
case Team::Terrorist:
return "TE";
case Team::Unassigned:
default:
return "UN";
case Team::Spectator:
return "SP";
}
};
for (const auto &bot : bots) {
ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%-3.0f secs", bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", bot->m_team == Team::CT ? "CT" : "T", bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_notKilled ? "yes" : "no", cv_rotate_bots.bool_ () ? bot->m_stayTime - game.time () : 0.0f);
ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d\t%-3.4s\t%-3.0f secs", bot->index (), bot->pev->netname.chars (), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", botTeam (bot->m_team), bot->m_difficulty, static_cast <int> (bot->pev->frags), bot->m_notKilled ? "yes" : "no", cv_rotate_bots.bool_ () ? bot->m_stayTime - game.time () : 0.0f);
}
ctrl.msg ("%d bots", m_bots.length ());
}
@ -1160,6 +1177,12 @@ int BotManager::getPlayerPriority (edict_t *ent) {
if (bot->m_hasC4 || bot->m_isVIP || bot->m_hasHostage || bot->m_healthValue < ent->v.health) {
return bot->entindex () + highPrio;
}
auto task = bot->getCurrentTaskId ();
// higher priority if camping or hiding
if (task == Task::Camp || task == Task::Hide) {
return bot->entindex () + highPrio;
}
return bot->entindex ();
}
@ -1189,7 +1212,7 @@ void BotManager::erase (Bot *bot) {
}
if (!bot->m_kickedByRotation && cv_save_bots_names.bool_ ()) {
m_saveBotNames.emplaceLast (bot->pev->netname.chars ());
m_saveBotNames.emplaceLast (bot->pev->netname.str ());
}
bot->markStale ();
@ -1282,6 +1305,7 @@ void Bot::newRound () {
m_grenadeRequested = false;
m_moveToC4 = false;
m_defuseNotified = false;
m_duckDefuse = false;
m_duckDefuseCheckTime = 0.0f;
@ -1310,6 +1334,7 @@ void Bot::newRound () {
m_askCheckTime = rg.get (30.0f, 90.0f);
m_minSpeed = 260.0f;
m_prevSpeed = 0.0f;
m_prevVelocity = 0.0f;
m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance);
m_prevTime = game.time ();
m_lookUpdateTime = game.time ();
@ -1431,6 +1456,7 @@ void Bot::newRound () {
m_combatStrafeDir = Dodge::None;
m_fightStyle = Fight::None;
m_lastFightStyleCheck = 0.0f;
m_stuckTimestamp = 0.0f;
m_checkWeaponSwitch = true;
m_checkKnifeSwitch = true;
@ -1454,20 +1480,9 @@ void Bot::newRound () {
m_soundUpdateTime = 0.0f;
m_heardSoundTime = game.time ();
// clear its message queue
for (auto &msg : m_messageQueue) {
msg = BotMsg::None;
}
m_msgQueue.clear ();
m_nodeHistory.clear ();
m_ignoredBreakable.clear ();
// clear last trace
for (auto i = 0; i < TraceChannel::Num; ++i) {
m_lastTrace[i] = {};
m_traceSkip[i] = 0;
}
// and put buying into its message queue
pushMsgQueue (BotMsg::Buy);
startTask (Task::Normal, TaskPri::Normal, kInvalidNodeIndex, 0.0f, true);
@ -1673,7 +1688,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
}
}
}
Client &target = util.getClient (game.indexOfPlayer (ent));
auto &target = util.getClient (game.indexOfPlayer (ent));
// check if this player alive, and issue something
if ((target.flags & ClientFlags::Alive) && target.radio != 0 && strncmp (cmd, "menuselect", 10) == 0) {
@ -1704,19 +1719,19 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
void BotManager::notifyBombDefuse () {
// notify all terrorists that CT is starting bomb defusing
if (!isBombPlanted ()) {
return;
}
auto bombPos = graph.getBombOrigin ();
const auto &bombPos = graph.getBombOrigin ();
for (const auto &bot : bots) {
auto task = bot->getCurrentTaskId ();
if (bot->m_notKilled && task != Task::MoveToPosition && task != Task::DefuseBomb && task != Task::EscapeFromBomb) {
if (bot->m_team == Team::CT || bot->pev->origin.distanceSq (bombPos) < cr::sqrf (384.0f)) {
if (!bot->m_defuseNotified && bot->m_notKilled && task != Task::MoveToPosition && task != Task::DefuseBomb && task != Task::EscapeFromBomb) {
if (bot->m_team == Team::Terrorist && bot->pev->origin.distanceSq (bombPos) < cr::sqrf (384.0f)) {
bot->clearSearchNodes ();
bot->m_pathType = FindPath::Fast;
bot->m_position = bombPos;
bot->m_defuseNotified = true;
bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true);
}
}

View file

@ -379,7 +379,7 @@ void MessageDispatcher::netMsgBarTime () {
m_bot->m_hasProgressBar = true; // the progress bar on a hud
// notify bots about defusing has started
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) {
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_bot->m_team == Team::CT) {
bots.notifyBombDefuse ();
}
}
@ -560,7 +560,7 @@ void MessageDispatcher::ensureMessages () {
// re-register our message
m_wanted.foreach ([&] (const String &key, const int32_t &) {
add (key, GET_USER_MSG_ID (PLID, key.chars (), nullptr));
add (key, MUTIL_GetUserMsgID (PLID, key.chars (), nullptr));
});
}

View file

@ -8,11 +8,6 @@
#include <yapb.h>
int Bot::findBestGoal () {
auto pushToHistroy = [&] (int32_t goal) -> int32_t {
m_nodeHistory.push (goal);
return goal;
};
if (m_isCreature) {
if (!graph.m_terrorPoints.empty ()) {
return graph.m_terrorPoints.random ();
@ -110,7 +105,7 @@ int Bot::findBestGoal () {
else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) {
// send some terrorists to guard planted bomb
if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0f) {
return pushToHistroy (m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ()));
return m_chosenGoalIndex = findDefendNode (graph.getBombOrigin ());
}
}
else if (game.mapIs (MapFlags::Escape)) {
@ -149,7 +144,7 @@ int Bot::findBestGoal () {
if (goalDesire > tacticChoice) {
tactic = 3;
}
return pushToHistroy (findGoalPost (tactic, defensiveNodes, offensiveNodes));
return findGoalPost (tactic, defensiveNodes, offensiveNodes);
}
int Bot::findBestGoalWhenBombAction () {
@ -365,12 +360,8 @@ void Bot::resetCollision () {
void Bot::ignoreCollision () {
resetCollision ();
m_prevTime = game.time () + 1.2f;
m_lastCollTime = game.time () + 1.5f;
m_isStuck = false;
m_lastCollTime = game.time () + m_frameInterval * 4.0f;
m_checkTerrain = false;
m_prevSpeed = m_moveSpeed;
m_prevOrigin = pev->origin;
}
void Bot::doPlayerAvoidance (const Vector &normal) {
@ -417,13 +408,13 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
if (game.isNullEntity (m_hindrance)) {
return;
}
const float interval = m_frameInterval * (pev->velocity.lengthSq2d () > 0.0f ? 9.0f : 2.0f);
const float interval = m_frameInterval * (pev->velocity.lengthSq2d () > 0.0f ? 7.5f : 2.0f);
// use our movement angles, try to predict where we should be next frame
Vector right, forward;
m_moveAngles.angleVectors (&forward, &right, nullptr);
Vector predict = pev->origin + forward * m_moveSpeed * interval;
Vector predict = pev->origin + forward * pev->maxspeed * interval;
predict += right * m_strafeSpeed * interval;
predict += pev->velocity * interval;
@ -432,7 +423,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
auto nextFrameDistance = pev->origin.distanceSq (m_hindrance->v.origin + m_hindrance->v.velocity * interval);
// is player that near now or in future that we need to steer away?
if (movedDistance <= cr::sqrf (48.0f) || (distance <= cr::sqrf (56.0f) && nextFrameDistance < distance)) {
if (movedDistance <= cr::sqrf (64.0f) || (distance <= cr::sqrf (72.0f) && nextFrameDistance < distance)) {
auto dir = (pev->origin - m_hindrance->v.origin).normalize2d_apx ();
// to start strafing, we have to first figure out if the target is on the left side or right side
@ -443,7 +434,7 @@ void Bot::doPlayerAvoidance (const Vector &normal) {
setStrafeSpeed (normal, -pev->maxspeed);
}
if (distance < cr::sqrf (76.0f)) {
if (distance < cr::sqrf (80.0f)) {
if ((dir | forward.normalize2d_apx ()) < 0.0f) {
m_moveSpeed = -pev->maxspeed;
}
@ -455,15 +446,12 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
// if avoiding someone do not consider stuck
TraceResult tr {};
float checkSpeed = isDucking () ? 4.0f : 10.0f;
m_isStuck = false;
// standing still, no need to check?
if ((m_moveSpeed >= checkSpeed || m_strafeSpeed >= checkSpeed) && m_lastCollTime < game.time ()) {
if (m_lastCollTime < game.time () && getCurrentTaskId () != Task::Attack) {
// didn't we move enough previously?
if (movedDistance < 2.0f && m_prevSpeed >= 20.0f) {
if (movedDistance < 2.0f && (m_prevSpeed > 20.0f || m_prevVelocity < m_moveSpeed / 2)) {
m_prevTime = game.time (); // then consider being stuck
m_isStuck = true;
@ -474,7 +462,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
// not stuck yet
else {
// test if there's something ahead blocking the way
if (cantMoveForward (dirNormal, &tr) && !isOnLadder ()) {
if (!isOnLadder () && cantMoveForward (dirNormal, &tr)) {
if (cr::fzero (m_firstCollideTime)) {
m_firstCollideTime = game.time () + 0.2f;
}
@ -489,19 +477,24 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
// not stuck?
if (!m_isStuck) {
if (m_probeTime + 0.5f < game.time ()) {
if (m_probeTime + rg.get (0.5f, 1.0f) < game.time ()) {
resetCollision (); // reset collision memory if not being stuck for 0.5 secs
}
else {
// remember to keep pressing duck if it was necessary ago
// remember to keep pressing stuff if it was necessary ago
if (m_collideMoves[m_collStateIndex] == CollisionState::Duck && (isOnFloor () || isInWater ())) {
pev->button |= IN_DUCK;
}
else if (m_collideMoves[m_collStateIndex] == CollisionState::StrafeLeft) {
setStrafeSpeed (dirNormal, -pev->maxspeed);
}
else if (m_collideMoves[m_collStateIndex] == CollisionState::StrafeRight) {
setStrafeSpeed (dirNormal, pev->maxspeed);
}
}
return;
}
// bot is stuck, but not yet decided what to do?
if (m_collisionState == CollisionState::Undecided) {
uint32_t bits = 0;
@ -549,7 +542,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) {
dirLeft = true;
}
const auto &testDir = m_moveSpeed > 0.0f ? forward : -forward;
constexpr float blockDistance = 40.0f;
constexpr float blockDistance = 32.0f;
// now check which side is blocked
src = pev->origin + right * blockDistance;
@ -784,6 +777,14 @@ void Bot::moveToGoal () {
}
}
void Bot::resetMovement () {
pev->button = 0;
m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
m_moveAngles = nullptr;
}
void Bot::translateInput () {
if (m_duckTime >= game.time ()) {
pev->button |= IN_DUCK;
@ -1047,18 +1048,18 @@ bool Bot::updateNavigation () {
}
}
float desiredDistance = 8.0f;
float nodeDistance = pev->origin.distance (m_pathOrigin);
float desiredDistance = cr::sqrf (8.0f);
float nodeDistance = pev->origin.distanceSq (m_pathOrigin);
// initialize the radius for a special node type, where the node is considered to be reached
if (m_pathFlags & NodeFlag::Lift) {
desiredDistance = 50.0f;
desiredDistance = cr::sqrf (50.0f);
}
else if (isDucking () || (m_pathFlags & NodeFlag::Goal)) {
desiredDistance = 25.0f;
desiredDistance = cr::sqrf (25.0f);
}
else if (m_pathFlags & NodeFlag::Ladder) {
desiredDistance = 24.0f;
else if (isOnLadder () || (m_pathFlags & NodeFlag::Ladder)) {
desiredDistance = cr::sqrf (24.0f);
}
else if (m_currentTravelFlags & PathFlag::Jump) {
desiredDistance = 0.0f;
@ -1067,13 +1068,13 @@ bool Bot::updateNavigation () {
desiredDistance = 0.0f;
}
else if (isOccupiedNode (m_path->number)) {
desiredDistance = 96.0f;
desiredDistance = cr::sqrf (96.0f);
}
else {
desiredDistance = m_path->radius;
desiredDistance = cr::max (cr::sqrf (m_path->radius), desiredDistance);
}
// check if node has a special travelflag, so they need to be reached more precisely
// check if node has a special travel flags, so they need to be reached more precisely
for (const auto &link : m_path->links) {
if (link.flags != 0) {
desiredDistance = 0.0f;
@ -1082,11 +1083,11 @@ bool Bot::updateNavigation () {
}
// needs precise placement - check if we get past the point
if (desiredDistance < 22.0f && nodeDistance < 30.0f && m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= cr::sqrf (nodeDistance)) {
if (desiredDistance < cr::sqrf (22.0f) && nodeDistance < cr::sqrf (30.0f) && m_pathOrigin.distanceSq (pev->origin + pev->velocity * m_frameInterval) >= nodeDistance) {
desiredDistance = nodeDistance + 1.0f;
}
// this allows us to prevent stupid bot behaviour when he reaches almost end point of this route, but some one (other bot eg)
// this allows us to prevent stupid bot behavior when he reaches almost end point of this route, but some one (other bot eg)
// is sitting there, so the bot is unable to reach the node because of other player on it, and he starts to jumping and so on
// here we're clearing task memory data (important!), since task executor may restart goal with one from memory, so this process
// will go in cycle, and forcing bot to re-create new route.
@ -1099,7 +1100,6 @@ bool Bot::updateNavigation () {
}
if (nodeDistance < desiredDistance) {
// did we reach a destination node?
if (getTask ()->data == m_currentNodeIndex) {
if (m_chosenGoalIndex != kInvalidNodeIndex) {
@ -1126,9 +1126,9 @@ bool Bot::updateNavigation () {
// bot within 'hearable' bomb tick noises?
if (!bombOrigin.empty ()) {
float distance = bombOrigin.distance (graph[taskTarget].origin);
float distance = bombOrigin.distanceSq (graph[taskTarget].origin);
if (distance > 512.0f) {
if (distance > cr::sqrf (512.0f)) {
if (rg.chance (50) && !graph.isVisited (taskTarget)) {
pushRadioMessage (Radio::SectorClear);
}
@ -2334,7 +2334,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
};
// trace from the bot's eyes straight forward...
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (tr->flFraction < 1.0f) {
@ -2349,7 +2349,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f;
forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f + normal * 24.0f;
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (checkDoor (tr)) {
@ -2361,7 +2361,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + right * 16.0f;
forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - right * -16.0f + normal * 24.0f;
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (checkDoor (tr)) {
@ -2373,7 +2373,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
src = pev->origin + Vector (0.0f, 0.0f, -19.0f + 19.0f);
forward = src + Vector (0.0f, 0.0f, 10.0f) + normal * 24.0f;
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (checkDoor (tr)) {
@ -2382,7 +2382,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
src = pev->origin;
forward = src + normal * 24.0f;
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (checkDoor (tr)) {
@ -2395,7 +2395,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f + normal * 24.0f;
// trace from the bot's waist straight forward...
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (checkDoor (tr)) {
@ -2406,7 +2406,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
src = pev->origin + Vector (0.0f, 0.0f, -24.0f) + right * 16.0f;
forward = pev->origin + Vector (0.0f, 0.0f, -24.0f) - right * -16.0f + normal * 24.0f;
traceResult = game.testLineChannel (TraceChannel::Body, src, forward, TraceIgnore::Monsters, ent (), *tr);
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
// check if the trace hit something...
if (checkDoor (tr)) {
@ -2717,7 +2717,7 @@ bool Bot::isBlockedRight () {
bool Bot::checkWallOnLeft () {
TraceResult tr {};
game.testLine (pev->origin, pev->origin - pev->angles.right () * 40.0f, TraceIgnore::Monsters, ent (), &tr);
game.testLine (pev->origin, pev->origin + -pev->angles.right () * 40.0f, TraceIgnore::Monsters, ent (), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f) {
@ -3040,25 +3040,6 @@ bool Bot::isReachableNode (int index) {
return false;
}
bool Bot::isBannedNode (int index) {
if (graph.exists (cv_debug_goal.int_ ()) || !graph.exists (index)) {
return false;
}
const auto &bucket = graph.getNodesInBucket (graph[index].origin);
// too few nodes in bucket near location, do not ban anything
if (bucket.length <int> () <= kMaxNodeLinks) {
return false;
}
for (const auto &node : m_nodeHistory) {
if (node == index) {
return true;
}
}
return false;
}
void Bot::findShortestPath (int srcIndex, int destIndex) {
// this function finds the shortest path from source index to destination index

View file

@ -418,6 +418,9 @@ void DijkstraAlgo::init (const int length) {
m_distance.resize (length);
m_parent.resize (length);
m_distance.shrink ();
m_parent.shrink ();
}
bool DijkstraAlgo::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, int *pathDistance) {
@ -432,15 +435,15 @@ bool DijkstraAlgo::find (int srcIndex, int destIndex, NodeAdderFn onAddedNode, i
m_distance[srcIndex] = 0;
while (!m_queue.empty ()) {
auto p = cr::move (m_queue.pop ());
auto current = p.second;
auto &&route = cr::move (m_queue.pop ());
auto current = route.second;
// finished search
if (current == destIndex) {
break;
}
if (m_distance[current] != p.first) {
if (m_distance[current] != route.first) {
continue;
}

View file

@ -69,7 +69,7 @@ void BotPractice::setDamage (int32_t team, int32_t start, int32_t goal, int32_t
float BotPractice::plannerGetDamage (int32_t team, int32_t start, int32_t goal, bool addTeamHighestDamage) {
if (!m_damageUpdateLock.tryLock ()) {
return 1.0f;
return 0.0f;
}
ScopedUnlock <Mutex> unlock (m_damageUpdateLock);
auto damage = static_cast <float> (getDamage (team, start, goal));

View file

@ -302,13 +302,13 @@ String BotStorage::buildPath (int32_t file, bool isMemoryLoad) {
using FilePath = Twin <String, String>;
static HashMap <int32_t, FilePath> paths = {
{ BotFile::Vistable, FilePath ("train", "vis")},
{ BotFile::Practice, FilePath ("train", "prc")},
{ BotFile::Pathmatrix, FilePath ("train", "pmx")},
{ BotFile::LogFile, FilePath ("logs", "txt")},
{ BotFile::Graph, FilePath ("graph", "graph")},
{ BotFile::PodbotPWF, FilePath ("pwf", "pwf")},
{ BotFile::EbotEWP, FilePath ("ewp", "ewp")},
{ BotFile::Vistable, FilePath (folders.train, "vis")},
{ BotFile::Practice, FilePath (folders.train, "prc")},
{ BotFile::Pathmatrix, FilePath (folders.train, "pmx")},
{ BotFile::LogFile, FilePath (folders.logs, "txt")},
{ BotFile::Graph, FilePath (folders.graph, "graph")},
{ BotFile::PodbotPWF, FilePath (folders.podbot, "pwf")},
{ BotFile::EbotEWP, FilePath (folders.ebot, "ewp")},
};
static StringArray path;
@ -320,11 +320,11 @@ String BotStorage::buildPath (int32_t file, bool isMemoryLoad) {
}
// allways append addons/product
path.emplace ("addons");
path.emplace (product.folder);
path.emplace (folders.addons);
path.emplace (folders.bot);
// the datadir
path.emplace ("data");
path.emplace (folders.data);
// append real filepath
path.emplace (paths[file].first);
@ -338,19 +338,19 @@ String BotStorage::buildPath (int32_t file, bool isMemoryLoad) {
auto timebuf = strings.chars ();
strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo);
path.emplace (strings.format ("%s_%s.%s", product.folder, timebuf, paths[file].second));
path.emplace (strings.format ("%s_%s.%s", folders.bot, timebuf, paths[file].second));
}
else {
String mapName = game.getMapName ();
path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second));
}
// finally use correct path separarators for us
// finally use correct path separators for us
return String::join (path, PATH_SEP);
}
int32_t BotStorage::storageToBotFile (int32_t options) {
// converts storage option to stroage filename
// converts storage option to storage filename
if (options & StorageOption::Graph) {
return BotFile::Graph;
@ -380,7 +380,7 @@ void BotStorage::unlinkFromDisk () {
unlinkable.emplace (buildPath (BotFile::Pathmatrix)); // corresponding to matrix
for (const auto &item : unlinkable) {
if (File::exists (item)) {
if (plat.fileExists (item.chars ())) {
plat.removeFile (item.chars ());
ctrl.msg ("File %s, has been deleted from the hard disk", item);
}
@ -388,7 +388,7 @@ void BotStorage::unlinkFromDisk () {
logger.error ("Unable to open %s", item);
}
}
graph.reset (); // re-intialize points
graph.reset (); // re-initialize points
}
#endif // BOT_STORAGE_EXPLICIT_INSTANTIATIONS

View file

@ -233,31 +233,31 @@ bool BotSupport::isFakeClient (edict_t *ent) {
return false;
}
bool BotSupport::openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) {
bool BotSupport::openConfig (StringRef fileName, StringRef errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) {
if (*outFile) {
outFile->close ();
}
// save config dir
auto configDir = strings.format ("addons/%s/conf", product.folder);
auto configDir = strings.joinPath (folders.addons, folders.bot, folders.config);
if (languageDependant) {
if (strcmp (fileName, "lang.cfg") == 0 && strcmp (cv_language.str (), "en") == 0) {
if (fileName.startsWith ("lang") && strcmp (cv_language.str (), "en") == 0) {
return false;
}
auto langConfig = strings.format ("%s/lang/%s_%s", configDir, cv_language.str (), fileName);
auto langConfig = strings.joinPath (configDir, folders.lang, strings.format ("%s_%s.%s", cv_language.str (), fileName, kConfigExtension));
// check is file is exists for this language
if (!outFile->open (langConfig)) {
outFile->open (strings.format ("%s/lang/en_%s", configDir, fileName));
outFile->open (strings.joinPath (configDir, folders.lang, strings.format ("en_%s.%s", fileName, kConfigExtension)));
}
}
else {
outFile->open (strings.format ("%s/%s", configDir, fileName));
outFile->open (strings.joinPath (configDir, strings.format ("%s.%s", fileName, kConfigExtension)));
}
if (!*outFile) {
logger.error (errorIfNotExists);
logger.error (errorIfNotExists.chars ());
return false;
}
return true;
@ -473,9 +473,6 @@ void BotSupport::syncCalculatePings () {
void BotSupport::emitPings (edict_t *to) {
MessageWriter msg;
// missing from sdk
constexpr int kGamePingSVC = 17;
auto isThirdpartyBot = [] (edict_t *ent) {
return !bots[ent] && (ent->v.flags & FL_FAKECLIENT);
};
@ -490,7 +487,7 @@ void BotSupport::emitPings (edict_t *to) {
client.ping = getPingBitmask (client.ent, rg.get (5, 10), rg.get (15, 40));
}
msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullptr, to)
msg.start (MSG_ONE_UNRELIABLE, SVC_PINGS, nullptr, to)
.writeLong (client.ping)
.end ();
}

View file

@ -48,7 +48,7 @@ void Bot::normal_ () {
getTask ()->data = kInvalidNodeIndex;
}
// reached the destination (goal) waypoint?
// reached the destination (goal) node?
if (updateNavigation ()) {
// if we're reached the goal, and there is not enemies, notify the team
if (!bots.isBombPlanted () && m_currentNodeIndex != kInvalidNodeIndex && (m_pathFlags & NodeFlag::Goal) && rg.chance (15) && numEnemiesNear (pev->origin, 650.0f) == 0) {
@ -65,7 +65,7 @@ void Bot::normal_ () {
}
}
// reached waypoint is a camp waypoint
// reached node is a camp node
if ((m_pathFlags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && cv_camping_allowed.bool_ () && !isKnifeMode ()) {
// check if bot has got a primary weapon and hasn't camped before
@ -184,22 +184,15 @@ void Bot::normal_ () {
}
// no more nodes to follow - search new ones (or we have a bomb)
else if (!hasActiveGoal ()) {
m_moveSpeed = pev->maxspeed;
clearSearchNodes ();
ignoreCollision ();
// did we already decide about a goal before?
auto currIndex = getTask ()->data;
auto destIndex = graph.exists (currIndex) && !isBannedNode (currIndex) && m_prevGoalIndex != currIndex ? currIndex : findBestGoal ();
auto destIndex = graph.exists (currIndex) ? currIndex : findBestGoal ();
// check for existance (this is failover, for i.e. csdm, this should be not true with normal gameplay, only when spawned outside of waypointed area)
// check for existence (this is fail over, for i.e. csdm, this should be not true with normal game play, only when spawned outside of covered area)
if (!graph.exists (destIndex)) {
destIndex = graph.getFarest (pev->origin, 512.0f);
}
if (!graph.exists (cv_debug_goal.int_ ()) && graph.exists (currIndex) && m_prevGoalIndex == currIndex && !(graph[currIndex].flags & NodeFlag::Goal)) {
m_nodeHistory.push (currIndex);
destIndex = graph.getFarest (pev->origin, 1024.0f);
}
m_prevGoalIndex = destIndex;
@ -210,7 +203,7 @@ void Bot::normal_ () {
// override with fast path
if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) {
pathSearchType = rg.chance (60) ? FindPath::Fast : FindPath::Optimal;
pathSearchType = rg.chance (80) ? FindPath::Fast : FindPath::Optimal;
}
ensureCurrentNodeIndex ();
@ -314,8 +307,6 @@ void Bot::huntEnemy_ () {
// do we need to calculate a new path?
else if (!hasActiveGoal ()) {
clearSearchNodes ();
int destIndex = kInvalidNodeIndex;
int goal = getTask ()->data;
@ -410,7 +401,6 @@ void Bot::seekCover_ () {
m_checkTerrain = false;
}
else if (!hasActiveGoal ()) {
clearSearchNodes ();
int destIndex = kInvalidNodeIndex;
if (getTask ()->data != kInvalidNodeIndex) {
@ -518,7 +508,6 @@ void Bot::blind_ () {
m_states |= Sense::SuspectEnemy;
}
else if (!hasActiveGoal ()) {
clearSearchNodes ();
ensureCurrentNodeIndex ();
m_prevGoalIndex = m_blindNodeIndex;
@ -707,8 +696,7 @@ void Bot::moveToPos_ () {
}
auto ensureDestIndexOK = [&] (int &index) {
if (isOccupiedNode (index) || isBannedNode (index)) {
m_nodeHistory.push (index);
if (isOccupiedNode (index)) {
index = findDefendNode (m_position);
}
};
@ -723,8 +711,6 @@ void Bot::moveToPos_ () {
// didn't choose goal waypoint yet?
else if (!hasActiveGoal ()) {
clearSearchNodes ();
int destIndex = kInvalidNodeIndex;
int goal = getTask ()->data;
@ -1050,8 +1036,6 @@ void Bot::followUser_ () {
// didn't choose goal waypoint yet?
if (!hasActiveGoal ()) {
clearSearchNodes ();
int destIndex = graph.getNearest (m_targetEntity->v.origin);
auto points = graph.getNarestInRadius (200.0f, m_targetEntity->v.origin);
@ -1306,8 +1290,6 @@ void Bot::doublejump_ () {
// didn't choose goal waypoint yet?
if (!hasActiveGoal ()) {
clearSearchNodes ();
int destIndex = graph.getNearest (m_doubleJumpOrigin);
if (graph.exists (destIndex)) {
@ -1359,8 +1341,6 @@ void Bot::escapeFromBomb_ () {
// didn't choose goal waypoint yet?
else if (!hasActiveGoal ()) {
clearSearchNodes ();
int bestIndex = kInvalidNodeIndex;
float safeRadius = rg.get (1513.0f, 2048.0f);

View file

@ -132,6 +132,7 @@ void Bot::updateAimDir () {
auto doFailPredict = [this] () {
m_aimFlags &= ~AimFlags::PredictPath;
m_trackingEdict = nullptr;
m_lookAt = m_destOrigin;
};
if (changePredictedEnemy) {
@ -147,7 +148,7 @@ void Bot::updateAimDir () {
}
}
if (graph.exists (predictNode) && pathLength < cv_max_nodes_for_predict.int_ ()) {
if (predictNode != kInvalidNodeIndex && pathLength < cv_max_nodes_for_predict.int_ ()) {
m_lookAt = graph[predictNode].origin;
m_lookAtSafe = m_lookAt;