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
|
|
@ -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
115
inc/fakeping.h
Normal 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);
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
129
src/fakeping.cpp
Normal 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> ());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {};
|
||||||
|
|
|
||||||
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_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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue