fix: pingfaker overwrites pings for real players (resolves #572)

fix: crash bug introduced in #569
This commit is contained in:
jeefo 2024-05-22 11:13:52 +03:00
commit ee964e0c9a
No known key found for this signature in database
GPG key ID: D696786B81B667C8
14 changed files with 322 additions and 188 deletions

View file

@ -25,6 +25,7 @@ set(YAPB_SRC
"src/config.cpp" "src/config.cpp"
"src/control.cpp" "src/control.cpp"
"src/engine.cpp" "src/engine.cpp"
"src/fakeping.cpp"
"src/graph.cpp" "src/graph.cpp"
"src/hooks.cpp" "src/hooks.cpp"
"src/linkage.cpp" "src/linkage.cpp"

115
inc/fakeping.h Normal file
View file

@ -0,0 +1,115 @@
//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// SPDX-License-Identifier: MIT
//
#pragma once
// adopted from pingfaker amxx plugin
class PingBitMsg final {
private:
int32_t bits_ {};
int32_t used_ {};
MessageWriter msg_ {};
bool started_ {};
public:
enum : int32_t {
Single = 1,
PlayerID = 5,
Loss = 7,
Ping = 12
};
public:
PingBitMsg () = default;
~PingBitMsg () = default;
public:
void write (int32_t bit, int32_t size) {
if (size > 32 - used_ || size < 1) {
return;
}
const auto maxSize = cr::bit (size);
if (bit >= maxSize) {
bit = maxSize - 1;
}
bits_ = bits_ + (bit << used_);
used_ += size;
}
void send (bool remaining = false) {
while (used_ >= 8) {
msg_.writeByte (bits_ & (cr::bit (8) - 1));
bits_ = (bits_ >> 8);
used_ -= 8;
}
if (remaining && used_ > 0) {
msg_.writeByte (bits_);
bits_ = used_ = 0;
}
}
void start (edict_t *ent) {
if (started_) {
return;
}
msg_.start (MSG_ONE_UNRELIABLE, SVC_PINGS, nullptr, ent);
started_ = true;
}
void flush () {
if (!started_) {
return;
}
write (0, Single);
send (true);
started_ = false;
msg_.end ();
}
};
// bot fakeping manager
class BotFakePingManager final : public Singleton <BotFakePingManager> {
private:
mutable Mutex m_cs {};
private:
CountdownTimer m_recalcTime {};
public:
explicit BotFakePingManager () = default;
~BotFakePingManager () = default;
public:
// verify game supports fakeping and it's enabled
bool hasFeature () const;
// reset the ping on disconnecting player
void reset (edict_t *ent);
// calculate our own pings for all the bots
void syncCalculate ();
// calculate our own pings for all the bots
void calculate ();
// emit pings in update client data hook
void emit (edict_t *ent);
// resetarts update timers
void restartTimer ();
// get random base ping
int randomBase () const;
};
// expose fakeping manager
CR_EXPOSE_GLOBAL_SINGLETON (BotFakePingManager, fakeping);

View file

@ -8,9 +8,6 @@
#pragma once #pragma once
class BotSupport final : public Singleton <BotSupport> { class BotSupport final : public Singleton <BotSupport> {
private:
mutable Mutex m_cs {};
private: private:
bool m_needToSendWelcome {}; bool m_needToSendWelcome {};
float m_welcomeReceiveTime {}; float m_welcomeReceiveTime {};
@ -70,21 +67,6 @@ public:
// update stats on clients // update stats on clients
void updateClients (); void updateClients ();
// generates ping bitmask for SVC_PINGS message
int getPingBitmask (edict_t *ent, int loss, int ping);
// calculate our own pings for all the players
void syncCalculatePings ();
// calculate our own pings for all the players
void calculatePings ();
// send modified pings to all the clients
void emitPings (edict_t *to);
// reset ping to zero values
void resetPings (edict_t *to);
// checks if same model omitting the models directory // checks if same model omitting the models directory
bool isModel (const edict_t *ent, StringRef model); bool isModel (const edict_t *ent, StringRef model);

View file

@ -103,7 +103,6 @@ struct Client {
int flags; // client flags int flags; // client flags
int radio; // radio orders int radio; // radio orders
int menu; // identifier to opened menu int menu; // identifier to opened menu
int ping; // when bot latency is enabled, client ping stored here
int iconFlags[kGameMaxPlayers]; // flag holding chatter icons int iconFlags[kGameMaxPlayers]; // flag holding chatter icons
float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons
ClientNoise noise; ClientNoise noise;
@ -589,6 +588,9 @@ public:
int m_difficulty {}; // bots hard level int m_difficulty {}; // bots hard level
int m_moneyAmount {}; // amount of money in bot's bank int m_moneyAmount {}; // amount of money in bot's bank
int m_pingBase {}; // base ping level for randomizing
int m_ping {}; // bot's acutal ping
float m_spawnTime {}; // time this bot spawned float m_spawnTime {}; // time this bot spawned
float m_timeTeamOrder {}; // time of last radio command float m_timeTeamOrder {}; // time of last radio command
float m_slowFrameTimestamp {}; // time to per-second think float m_slowFrameTimestamp {}; // time to per-second think
@ -627,7 +629,6 @@ public:
int m_blindNodeIndex {}; // node index to cover when blind int m_blindNodeIndex {}; // node index to cover when blind
int m_flashLevel {}; // flashlight level int m_flashLevel {}; // flashlight level
int m_basePing {}; // base ping for bot
int m_numEnemiesLeft {}; // number of enemies alive left on map int m_numEnemiesLeft {}; // number of enemies alive left on map
int m_numFriendsLeft {}; // number of friend alive left on map int m_numFriendsLeft {}; // number of friend alive left on map
int m_retryJoin {}; // retry count for choosing team/class int m_retryJoin {}; // retry count for choosing team/class
@ -875,6 +876,7 @@ private:
#include "planner.h" #include "planner.h"
#include "storage.h" #include "storage.h"
#include "analyze.h" #include "analyze.h"
#include "fakeping.h"
// very global convars // very global convars
extern ConVar cv_jasonmode; extern ConVar cv_jasonmode;

View file

@ -260,6 +260,7 @@ sources = files(
'src/config.cpp', 'src/config.cpp',
'src/control.cpp', 'src/control.cpp',
'src/engine.cpp', 'src/engine.cpp',
'src/fakeping.cpp',
'src/graph.cpp', 'src/graph.cpp',
'src/hooks.cpp', 'src/hooks.cpp',
'src/linkage.cpp', 'src/linkage.cpp',

View file

@ -975,15 +975,17 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// if we're have a glock or famas vary burst fire mode // if we're have a glock or famas vary burst fire mode
checkBurstMode (distance); checkBurstMode (distance);
if (hasShield () && m_shieldCheckTime < game.time () && getCurrentTaskId () != Task::Camp) // better shield gun usage // better shield gun usage
{ if (hasShield () && m_shieldCheckTime < game.time () && getCurrentTaskId () != Task::Camp) {
const bool hasEnemy = !game.isNullEntity (m_enemy);
if (distance >= 750.0f && !isShieldDrawn ()) { if (distance >= 750.0f && !isShieldDrawn ()) {
pev->button |= IN_ATTACK2; // draw the shield pev->button |= IN_ATTACK2; // draw the shield
} }
else if (isShieldDrawn () else if (isShieldDrawn ()
|| m_isReloading || m_isReloading
|| !seesEntity (m_enemy->v.origin) || (hasEnemy && (m_enemy->v.button & IN_RELOAD))
|| (!game.isNullEntity (m_enemy) && (m_enemy->v.button & IN_RELOAD))) { || (hasEnemy && !seesEntity (m_enemy->v.origin))) {
pev->button |= IN_ATTACK2; // draw out the shield pev->button |= IN_ATTACK2; // draw out the shield
} }

View file

@ -90,6 +90,9 @@ void Game::levelInitialize (edict_t *entities, int max) {
// set the global timer function // set the global timer function
timerStorage.setTimeAddress (&globals->time); timerStorage.setTimeAddress (&globals->time);
// reset timer
fakeping.restartTimer ();
// go thru the all entities on map, and do whatever we're want // go thru the all entities on map, and do whatever we're want
for (int i = 0; i < max; ++i) { for (int i = 0; i < max; ++i) {
auto ent = entities + i; auto ent = entities + i;
@ -1007,9 +1010,6 @@ void Game::slowFrame () {
// refresh bomb origin in case some plugin moved it out // refresh bomb origin in case some plugin moved it out
graph.setBombOrigin (); graph.setBombOrigin ();
// update client pings
util.calculatePings ();
// update next update time // update next update time
m_halfSecondFrame = nextUpdate * 0.25f + time (); m_halfSecondFrame = nextUpdate * 0.25f + time ();
} }
@ -1049,6 +1049,9 @@ void Game::slowFrame () {
// refresh bot infection (creature) status // refresh bot infection (creature) status
bots.refreshCreatureStatus (); bots.refreshCreatureStatus ();
// update client pings
fakeping.calculate ();
// update next update time // update next update time
m_oneSecondFrame = nextUpdate + time (); m_oneSecondFrame = nextUpdate + time ();
} }

129
src/fakeping.cpp Normal file
View file

@ -0,0 +1,129 @@
//
// YaPB, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright © YaPB Project Developers <yapb@jeefo.net>.
//
// SPDX-License-Identifier: MIT
//
#include <yapb.h>
ConVar cv_ping_base_min ("ping_base_min", "5", "Lower bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f);
ConVar cv_ping_base_max ("ping_base_max", "20", "Upper bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f);
ConVar cv_ping_count_real_players ("ping_count_real_players", "1", "Count player pings when calculating average ping for bots. If no, some random ping chosen for bots.");
ConVar cv_ping_updater_interval ("ping_updater_interval", "1.25", "Interval in which fakeping get updated in scoreboard.", true, 0.1f, 10.0f);
bool BotFakePingManager::hasFeature () const {
return game.is (GameFlags::HasFakePings) && cv_show_latency.as <int> () >= 2;
}
void BotFakePingManager::reset (edict_t *to) {
// no reset if game isn't support them
if (!hasFeature ()) {
return;
}
static PingBitMsg pbm {};
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) {
continue;
}
pbm.start (client.ent);
pbm.write (1, PingBitMsg::Single);
pbm.write (game.indexOfPlayer (to), PingBitMsg::PlayerID);
pbm.write (0, PingBitMsg::Ping);
pbm.write (0, PingBitMsg::Loss);
pbm.send ();
}
pbm.flush ();
}
void BotFakePingManager::syncCalculate () {
MutexScopedLock lock (m_cs);
int averagePing {};
if (cv_ping_count_real_players) {
int numHumans {};
for (const auto &client : util.getClients ()) {
if (!(client.flags & ClientFlags::Used) || util.isFakeClient (client.ent)) {
continue;
}
numHumans++;
int ping {}, loss {};
engfuncs.pfnGetPlayerStats (client.ent, &ping, &loss);
averagePing += ping > 0 && ping < 200 ? ping : randomBase ();
}
if (numHumans > 0) {
averagePing /= numHumans;
}
else {
averagePing = randomBase ();
}
}
else {
averagePing = randomBase ();
}
for (auto &bot : bots) {
auto botPing = static_cast <int> (bot->m_pingBase + rg (averagePing - averagePing * 0.2f, averagePing + averagePing * 0.2f) + rg (bot->m_difficulty + 3, bot->m_difficulty + 6));
if (botPing < 5) {
botPing = rg (10, 15);
}
else if (botPing > 100) {
botPing = rg (30, 40);
}
bot->m_ping = static_cast <int> (static_cast <float> (bot->entindex () % 2 == 0 ? botPing * 0.25f : botPing * 0.5f));
}
}
void BotFakePingManager::calculate () {
if (!hasFeature ()) {
return;
}
// throttle updating
if (!m_recalcTime.elapsed ()) {
return;
}
restartTimer ();
worker.enqueue ([this] () {
syncCalculate ();
});
}
void BotFakePingManager::emit (edict_t *ent) {
if (!util.isPlayer (ent)) {
return;
}
static PingBitMsg pbm {};
for (const auto &bot : bots) {
pbm.start (ent);
pbm.write (1, PingBitMsg::Single);
pbm.write (bot->entindex () - 1, PingBitMsg::PlayerID);
pbm.write (bot->m_ping, PingBitMsg::Ping);
pbm.write (0, PingBitMsg::Loss);
pbm.send ();
}
pbm.flush ();
}
void BotFakePingManager::restartTimer () {
m_recalcTime.start (cv_ping_updater_interval.as <float> ());
}
int BotFakePingManager::randomBase () const {
return rg (cv_ping_base_min.as <int> (), cv_ping_base_max.as <int> ());
}

View file

@ -246,6 +246,28 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
dllapi.pfnClientDisconnect (ent); dllapi.pfnClientDisconnect (ent);
}; };
table->pfnClientPutInServer = [] (edict_t *ent) {
// this function is called once a just connected client actually enters the game, after
// having downloaded and synchronized its resources with the of the server's. It's the
// perfect place to hook for client connecting, since a client can always try to connect
// passing the ClientConnect() step, and not be allowed by the server later (because of a
// latency timeout or whatever reason). We can here keep track of both bots and players
// counts on occurence, since bots connect the server just like the way normal client do,
// and their third party bot flag is already supposed to be set then. If it's a bot which
// is connecting, we also have to awake its brain(s) by reading them from the disk.
// refresh pings when client connetcs
if (fakeping.hasFeature ()) {
fakeping.emit (ent);
}
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);
}
dllapi.pfnClientPutInServer (ent);
};
table->pfnClientUserInfoChanged = [] (edict_t *ent, char *infobuffer) { table->pfnClientUserInfoChanged = [] (edict_t *ent, char *infobuffer) {
// this function is called when a player changes model, or changes team. Occasionally it // this function is called when a player changes model, or changes team. Occasionally it
// enforces rules on these changes (for example, some MODs don't want to allow players to // enforces rules on these changes (for example, some MODs don't want to allow players to
@ -389,22 +411,18 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
bots.frame (); bots.frame ();
}; };
if (game.is (GameFlags::HasFakePings)) { if (game.is (GameFlags::HasFakePings) && !game.is (GameFlags::Metamod)) {
table->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) { table->pfnUpdateClientData = [] (const struct edict_s *player, int sendweapons, struct clientdata_s *cd) {
auto ent = const_cast <edict_t *> (player); dllapi.pfnUpdateClientData (player, sendweapons, cd);
// if we're handle pings for bots and clients, clear IN_SCORE button so SV_ShouldUpdatePing engine function return false, and SV_EmitPings will not overwrite our results // do a post-processing with non-metamod
if (cv_show_latency.as <int> () == 2) { auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player));
if (!util.isFakeClient (ent) && (ent->v.oldbuttons | ent->v.button | cmd->buttons) & IN_SCORE) {
cmd->buttons &= ~IN_SCORE; if (fakeping.hasFeature ()) {
util.emitPings (ent); if (!util.isFakeClient (ent) && (ent->v.oldbuttons | ent->v.button) & IN_SCORE) {
fakeping.emit (ent);
} }
} }
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);
}
dllapi.pfnCmdStart (ent, cmd, random_seed);
}; };
} }
@ -479,6 +497,20 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
RETURN_META (MRES_IGNORED); RETURN_META (MRES_IGNORED);
}; };
if (game.is (GameFlags::HasFakePings)) {
table->pfnUpdateClientData = [] (const struct edict_s *player, int, struct clientdata_s *) {
// do a post-processing with non-metamod
auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player));
if (fakeping.hasFeature ()) {
if (!util.isFakeClient (ent) && (ent->v.oldbuttons | ent->v.button) & IN_SCORE) {
fakeping.emit (ent);
}
}
RETURN_META (MRES_IGNORED);
};
}
return HLTrue; return HLTrue;
} }

View file

@ -39,9 +39,6 @@ ConVar cv_botskin_t ("botskin_t", "0", "Specifies the bots wanted skin for Terro
ConVar cv_botskin_ct ("botskin_ct", "0", "Specifies the bots wanted skin for CT team.", true, 0.0f, 5.0f); ConVar cv_botskin_ct ("botskin_ct", "0", "Specifies the bots wanted skin for CT team.", true, 0.0f, 5.0f);
ConVar cv_preferred_personality ("preferred_personality", "none", "Sets the default personality when creating bots with quota management.\nAllowed values: 'none', 'normal', 'careful', 'rusher'.\nIf 'none' is specified personality chosen randomly.", false); ConVar cv_preferred_personality ("preferred_personality", "none", "Sets the default personality when creating bots with quota management.\nAllowed values: 'none', 'normal', 'careful', 'rusher'.\nIf 'none' is specified personality chosen randomly.", false);
ConVar cv_ping_base_min ("ping_base_min", "7", "Lower bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f);
ConVar cv_ping_base_max ("ping_base_max", "34", "Upper bound for base bot ping shown in scoreboard upon creation.", true, 0.0f, 100.0f);
ConVar cv_quota_adding_interval ("quota_adding_interval", "0.10", "Interval in which bots are added to the game.", true, 0.10f, 1.0f); ConVar cv_quota_adding_interval ("quota_adding_interval", "0.10", "Interval in which bots are added to the game.", true, 0.10f, 1.0f);
ConVar cv_quota_maintain_interval ("quota_maintain_interval", "0.40", "Interval on which overall bot quota are checked.", true, 0.40f, 2.0f); ConVar cv_quota_maintain_interval ("quota_maintain_interval", "0.40", "Interval on which overall bot quota are checked.", true, 0.40f, 2.0f);
@ -1161,7 +1158,8 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int skin) {
} }
m_difficulty = rg (minDifficulty, maxDifficulty); m_difficulty = rg (minDifficulty, maxDifficulty);
} }
m_basePing = rg (cv_ping_base_min.as <int> (), cv_ping_base_max.as <int> ()); m_pingBase = fakeping.randomBase ();
m_ping = fakeping.randomBase ();
m_previousThinkTime = game.time () - 0.1f; m_previousThinkTime = game.time () - 0.1f;
m_frameInterval = game.time (); m_frameInterval = game.time ();
@ -1526,7 +1524,10 @@ void Bot::newRound () {
m_followWaitTime = 0.0f; m_followWaitTime = 0.0f;
m_hostages.clear (); m_hostages.clear ();
m_approachingLadderTimer.invalidate ();
m_forgetLastVictimTimer.invalidate (); m_forgetLastVictimTimer.invalidate ();
m_lostReachableNodeTimer.invalidate ();
for (auto &timer : m_chatterTimes) { for (auto &timer : m_chatterTimes) {
timer = kMaxChatterRepeatInterval; timer = kMaxChatterRepeatInterval;
@ -1713,7 +1714,7 @@ void Bot::markStale () {
showChatterIcon (false, true); showChatterIcon (false, true);
// reset bots ping to default // reset bots ping to default
util.resetPings (ent ()); fakeping.reset (ent ());
// mark bot as leaving // mark bot as leaving
m_isStale = true; m_isStale = true;

View file

@ -385,7 +385,7 @@ void Bot::postProcessGoals (const IntArray &goals, int result[]) {
return true; return true;
} }
} }
return false; return isOccupiedNode (index, true);
}; };
static IntArray resulting {}; static IntArray resulting {};

View file

@ -11,7 +11,6 @@ ConVar cv_display_welcome_text ("display_welcome_text", "1", "Enables or disable
ConVar cv_enable_query_hook ("enable_query_hook", "0", "Enables or disables fake server queries response, that shows bots as real players in server browser."); ConVar cv_enable_query_hook ("enable_query_hook", "0", "Enables or disables fake server queries response, that shows bots as real players in server browser.");
ConVar cv_breakable_health_limit ("breakable_health_limit", "500.0", "Specifies the maximum health of breakable object, that bot will consider to destroy.", true, 1.0f, 3000.0); ConVar cv_breakable_health_limit ("breakable_health_limit", "500.0", "Specifies the maximum health of breakable object, that bot will consider to destroy.", true, 1.0f, 3000.0);
ConVar cv_enable_fake_steamids ("enable_fake_steamids", "0", "Allows or disallows bots to return fake steam id."); ConVar cv_enable_fake_steamids ("enable_fake_steamids", "0", "Allows or disallows bots to return fake steam id.");
ConVar cv_count_players_for_fakeping ("count_players_for_fakeping", "1", "Count player pings when calculating average ping for bots. If no, some random ping chosen for bots.");
BotSupport::BotSupport () { BotSupport::BotSupport () {
m_needToSendWelcome = false; m_needToSendWelcome = false;
@ -407,147 +406,6 @@ void BotSupport::updateClients () {
} }
} }
int BotSupport::getPingBitmask (edict_t *ent, int loss, int ping) {
// this function generates bitmask for SVC_PINGS engine message
// see:
// https://github.com/dreamstalker/rehlds/blob/a680f18ee1e7eb8c39fbdc45682163ca9477d783/rehlds/engine/sv_main.cpp#L4590
const auto emit = [] (int s0, int s1, int s2) {
return (s0 & (cr::bit (s1) - 1)) << s2;
};
return emit (loss, 7, 18) | emit (ping, 12, 6) | emit (game.indexOfPlayer (ent), 5, 1) | 1;
}
void BotSupport::calculatePings () {
worker.enqueue ([this] () {
syncCalculatePings ();
});
}
void BotSupport::syncCalculatePings () {
if (!game.is (GameFlags::HasFakePings) || cv_show_latency.as <int> () != 2) {
return;
}
MutexScopedLock lock (m_cs);
Twin <int, int> average { 0, 0 };
int numHumans = 0;
// only count player pings if we're allowed to
if (cv_count_players_for_fakeping) {
// first get average ping on server, and store real client pings
for (auto &client : m_clients) {
if (!(client.flags & ClientFlags::Used) || isFakeClient (client.ent)) {
continue;
}
int ping {}, loss {};
engfuncs.pfnGetPlayerStats (client.ent, &ping, &loss);
// @note: for those who asking on a email, we CAN call pfnGetPlayerStats hl-engine function in a separate thread
// since the function doesn't modify anything inside engine, so race-condition and crash isn't viable situation
// it's just fills ping and loss from engine structures, the only way to cause crash in separate thread
// is to call it with a invalid ``client`` pointer (on goldsrc), thus causing Con_Printf which is not compatible with
// multi-threaded environment
//
// see:
// https://github.com/dreamstalker/rehlds/blob/a680f18ee1e7eb8c39fbdc45682163ca9477d783/rehlds/engine/pr_cmds.cpp#L2735C15-L2735C32
// https://github.com/fwgs/xash3d-fwgs/blob/f5b9826fd9bbbdc5293c1ff522de11ce28d3c9f2/engine/server/sv_game.c#L4443
// store normal client ping
client.ping = getPingBitmask (client.ent, loss, ping > 0 ? ping : rg (8, 16)); // getting player ping sometimes fails
++numHumans;
average.first += ping;
average.second += loss;
}
if (numHumans > bots.getBotCount () / 4) {
average.first /= numHumans;
average.second /= numHumans;
}
else {
average.first = rg (10, 20);
average.second = rg (5, 10);
}
}
else {
average.first = rg (10, 20);
average.second = rg (5, 10);
}
// now calculate bot ping based on average from players
for (auto &client : m_clients) {
if (!(client.flags & ClientFlags::Used)) {
continue;
}
auto bot = bots[client.ent];
// we're only interested in bots here
if (!bot) {
continue;
}
const int part = static_cast <int> (static_cast <float> (average.first) * 0.2f);
int botPing = bot->m_basePing + rg (average.first - part, average.first + part) + rg (bot->m_difficulty / 2, bot->m_difficulty);
const int botLoss = rg (average.second / 2, average.second);
if (botPing < 2) {
botPing = rg (10, 23);
}
else if (botPing > 100) {
botPing = rg (30, 40);
}
client.ping = getPingBitmask (client.ent, botLoss, botPing);
}
}
void BotSupport::emitPings (edict_t *to) {
static MessageWriter msg {};
auto isThirdpartyBot = [] (edict_t *ent) {
return !bots[ent] && (ent->v.flags & FL_FAKECLIENT);
};
for (auto &client : m_clients) {
if (!(client.flags & ClientFlags::Used) || client.ent == game.getLocalEntity () || isThirdpartyBot (client.ent)) {
continue;
}
// do not send to dormants
if (client.ent->v.flags & FL_DORMANT) {
continue;
}
// no ping, no fun
if (!client.ping) {
client.ping = getPingBitmask (client.ent, rg (5, 10), rg (15, 40));
}
msg.start (MSG_ONE_UNRELIABLE, SVC_PINGS, nullptr, to)
.writeLong (client.ping)
.end ();
}
}
void BotSupport::resetPings (edict_t *to) {
static MessageWriter msg {};
// no reset if game isn't support them
if (!game.is (GameFlags::HasFakePings)) {
return;
}
for (auto &client : m_clients) {
if (!(client.flags & ClientFlags::Used) || isFakeClient (client.ent)) {
continue;
}
msg.start (MSG_ONE_UNRELIABLE, SVC_PINGS, nullptr, client.ent)
.writeLong (getPingBitmask (to, 0, 0))
.end ();
}
}
bool BotSupport::isModel (const edict_t *ent, StringRef model) { bool BotSupport::isModel (const edict_t *ent, StringRef model) {
return model.startsWith (ent->v.model.chars (9)); return model.startsWith (ent->v.model.chars (9));
} }

View file

@ -60,6 +60,7 @@
<ClInclude Include="..\inc\constant.h" /> <ClInclude Include="..\inc\constant.h" />
<ClInclude Include="..\inc\control.h" /> <ClInclude Include="..\inc\control.h" />
<ClInclude Include="..\inc\engine.h" /> <ClInclude Include="..\inc\engine.h" />
<ClInclude Include="..\inc\fakeping.h" />
<ClInclude Include="..\inc\graph.h" /> <ClInclude Include="..\inc\graph.h" />
<ClInclude Include="..\inc\hooks.h" /> <ClInclude Include="..\inc\hooks.h" />
<ClInclude Include="..\inc\manager.h" /> <ClInclude Include="..\inc\manager.h" />
@ -98,6 +99,7 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="..\src\fakeping.cpp" />
<ClCompile Include="..\src\graph.cpp" /> <ClCompile Include="..\src\graph.cpp" />
<ClCompile Include="..\src\hooks.cpp" /> <ClCompile Include="..\src\hooks.cpp" />
<ClCompile Include="..\src\linkage.cpp" /> <ClCompile Include="..\src\linkage.cpp" />

View file

@ -192,6 +192,9 @@
<ClInclude Include="..\inc\hooks.h"> <ClInclude Include="..\inc\hooks.h">
<Filter>inc</Filter> <Filter>inc</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\inc\fakeping.h">
<Filter>inc</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="..\src\botlib.cpp"> <ClCompile Include="..\src\botlib.cpp">
@ -263,6 +266,9 @@
<ClCompile Include="..\src\hooks.cpp"> <ClCompile Include="..\src\hooks.cpp">
<Filter>src</Filter> <Filter>src</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\src\fakeping.cpp">
<Filter>src</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="yapb.rc"> <ResourceCompile Include="yapb.rc">