fix: pingfaker overwrites pings for real players (resolves #572)
fix: crash bug introduced in #569
This commit is contained in:
parent
747eb569c1
commit
ee964e0c9a
14 changed files with 322 additions and 188 deletions
142
src/support.cpp
142
src/support.cpp
|
|
@ -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_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_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 () {
|
||||
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) {
|
||||
return model.startsWith (ent->v.model.chars (9));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue