uploading nides edition of fakeclients with sm_debugfakes added
This commit is contained in:
parent
80d0b85367
commit
6acdc80767
80
FakeClients/configs/fakeclients.txt
Normal file
80
FakeClients/configs/fakeclients.txt
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
HAFL-LIFE_2
|
||||||
|
NoobDnoRaq
|
||||||
|
IMBA
|
||||||
|
ТАЩИР
|
||||||
|
ebaw-kak-t34
|
||||||
|
h4rdc0r3
|
||||||
|
omfg]wiz
|
||||||
|
GOdlikebAby
|
||||||
|
SHITSHITSHIT
|
||||||
|
-Impulse-
|
||||||
|
SLAVA_UKRAINE
|
||||||
|
4ik-pyk
|
||||||
|
BigBoss
|
||||||
|
lolkalalka
|
||||||
|
muhahahahaha
|
||||||
|
GFLClan.ru
|
||||||
|
nide.gg
|
||||||
|
vk.com/nide_css
|
||||||
|
alex481516
|
||||||
|
alahakbar
|
||||||
|
HeadShot
|
||||||
|
HolySHIT
|
||||||
|
CRAY_ZEE
|
||||||
|
mlpro
|
||||||
|
Deadmemories
|
||||||
|
MaGothic
|
||||||
|
ILoveYouBaby
|
||||||
|
nevazhno
|
||||||
|
Tauren
|
||||||
|
Calypso
|
||||||
|
Hawk
|
||||||
|
(1)HAFL-LIFE_2
|
||||||
|
(1)NoobDnoRaq
|
||||||
|
(1)Hawk
|
||||||
|
(1)Tauren
|
||||||
|
(1)mlpro
|
||||||
|
(1)CRAY_ZEE
|
||||||
|
(1)NF-CSS.RU
|
||||||
|
(1)Calypso
|
||||||
|
(1)BigBoss
|
||||||
|
HAFL-LIFE_2
|
||||||
|
NoobDnoRaq
|
||||||
|
IMBA
|
||||||
|
ТАЩИР
|
||||||
|
ebaw-kak-t34
|
||||||
|
h4rdc0r3
|
||||||
|
omfg]wiz
|
||||||
|
GOdlikebAby
|
||||||
|
SHITSHITSHIT
|
||||||
|
-Impulse-
|
||||||
|
SLAVA_UKRAINE
|
||||||
|
4ik-pyk
|
||||||
|
BigBoss
|
||||||
|
lolkalalka
|
||||||
|
muhahahahaha
|
||||||
|
cp.powerhub.ru
|
||||||
|
NiDE.GG
|
||||||
|
vk.com/nide_css
|
||||||
|
alex481516
|
||||||
|
alahakbar
|
||||||
|
HeadShot
|
||||||
|
HolySHIT
|
||||||
|
CRAY_ZEE
|
||||||
|
mlpro
|
||||||
|
Deadmemories
|
||||||
|
MaGothic
|
||||||
|
ILoveYouBaby
|
||||||
|
nevazhno
|
||||||
|
Tauren
|
||||||
|
Calypso
|
||||||
|
Hawk
|
||||||
|
(1)HAFL-LIFE_2
|
||||||
|
(1)NoobDnoRaq
|
||||||
|
(1)Hawk
|
||||||
|
(1)Tauren
|
||||||
|
(1)mlpro
|
||||||
|
(1)CRAY_ZEE
|
||||||
|
(1)NF-CSS.RU
|
||||||
|
(1)Calypso
|
||||||
|
(1)BigBoss
|
||||||
@ -1,114 +0,0 @@
|
|||||||
Dwelitram
|
|
||||||
Gwyri
|
|
||||||
Caredus
|
|
||||||
Arerawia
|
|
||||||
Vilali
|
|
||||||
Astiwiel
|
|
||||||
Vardonydd
|
|
||||||
Ybaossa
|
|
||||||
Umyk
|
|
||||||
Nico50Pax
|
|
||||||
Onand
|
|
||||||
Thelian
|
|
||||||
Nydaleth
|
|
||||||
Chomarin
|
|
||||||
Traedien
|
|
||||||
Miev
|
|
||||||
Kaaede
|
|
||||||
Koamond
|
|
||||||
TheRottenBenson
|
|
||||||
BigLegend2017
|
|
||||||
TRIGGEREDHarambexXx
|
|
||||||
InPepe2016
|
|
||||||
xXxMaster2012
|
|
||||||
InBoixXx
|
|
||||||
TheKopsing
|
|
||||||
Cornelius
|
|
||||||
Gustavo
|
|
||||||
Bryant
|
|
||||||
Winfred
|
|
||||||
Nicolas
|
|
||||||
Mitchel
|
|
||||||
Dana
|
|
||||||
Carrol
|
|
||||||
Darell
|
|
||||||
Ruben
|
|
||||||
Jeromy
|
|
||||||
Wade
|
|
||||||
Scotty
|
|
||||||
Salvatore
|
|
||||||
Kory
|
|
||||||
Don
|
|
||||||
Morgan
|
|
||||||
Kurtis
|
|
||||||
Federico
|
|
||||||
Darin
|
|
||||||
css-ru
|
|
||||||
aimbot
|
|
||||||
lastkraftwagenfahrzeug
|
|
||||||
edger
|
|
||||||
clownface
|
|
||||||
слово
|
|
||||||
счастливый
|
|
||||||
kara
|
|
||||||
puta
|
|
||||||
meow
|
|
||||||
uncle sam
|
|
||||||
FunBun
|
|
||||||
Counter-Strike.Com.Ua
|
|
||||||
For-css.Ru
|
|
||||||
BOBO
|
|
||||||
Z3r0
|
|
||||||
ZeTo
|
|
||||||
Sakharov
|
|
||||||
Sache11
|
|
||||||
Mr. Dogenberg
|
|
||||||
Maus
|
|
||||||
Magikarpet
|
|
||||||
Miles
|
|
||||||
magick
|
|
||||||
James Lebron
|
|
||||||
jiZZ
|
|
||||||
Dobel
|
|
||||||
THE POPE OF DOPE
|
|
||||||
The Joker
|
|
||||||
that guy over there
|
|
||||||
Be happy :)
|
|
||||||
KingKong
|
|
||||||
Figger Nucker
|
|
||||||
noname
|
|
||||||
alexdu63
|
|
||||||
I put babies in the microwave
|
|
||||||
Tango!!
|
|
||||||
faggot killer
|
|
||||||
admin pidaras
|
|
||||||
MESSI>RONALDO
|
|
||||||
Gabe Newell
|
|
||||||
acetylsalicylic acid
|
|
||||||
Country-Steak: Sauce
|
|
||||||
chlamydia harvester
|
|
||||||
kyle_69
|
|
||||||
Shaggy's dog
|
|
||||||
rEVERSE
|
|
||||||
Piment d'espelette
|
|
||||||
FireHawk
|
|
||||||
Sgt. Pepper
|
|
||||||
krix.
|
|
||||||
imPulse_^_
|
|
||||||
Lardon
|
|
||||||
CAPS LOCK
|
|
||||||
$pussymoneyweed$
|
|
||||||
Low5
|
|
||||||
Bonerfart
|
|
||||||
Shiny Thanos
|
|
||||||
MARK NUTT
|
|
||||||
Legless Runner
|
|
||||||
Banana is a berry
|
|
||||||
Gabriel
|
|
||||||
El Moustachio
|
|
||||||
James Bong
|
|
||||||
Disco Fever
|
|
||||||
Acacia
|
|
||||||
Sugared
|
|
||||||
ronald goddamn macdonald
|
|
||||||
pizza is life
|
|
||||||
27
FakeClients/configs/fakeclients_tiers.cfg
Normal file
27
FakeClients/configs/fakeclients_tiers.cfg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"FakeClientsTiers"
|
||||||
|
{
|
||||||
|
"0" "23"
|
||||||
|
"3" "22"
|
||||||
|
"6" "21"
|
||||||
|
"9" "20"
|
||||||
|
"12" "19"
|
||||||
|
"15" "18"
|
||||||
|
"18" "17"
|
||||||
|
"21" "16"
|
||||||
|
"24" "15"
|
||||||
|
"27" "14"
|
||||||
|
"30" "13"
|
||||||
|
"33" "12"
|
||||||
|
"36" "11"
|
||||||
|
"39" "10"
|
||||||
|
"42" "9"
|
||||||
|
"45" "8"
|
||||||
|
"48" "7"
|
||||||
|
"51" "6"
|
||||||
|
"54" "5"
|
||||||
|
"55" "4"
|
||||||
|
"57" "3"
|
||||||
|
"60" "2"
|
||||||
|
"61" "1"
|
||||||
|
"62" "0"
|
||||||
|
}
|
||||||
480
FakeClients/scripting/FakeClients_nide.sp
Normal file
480
FakeClients/scripting/FakeClients_nide.sp
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
#pragma semicolon 1
|
||||||
|
#pragma newdecls required
|
||||||
|
#include <sourcemod>
|
||||||
|
#include <sdktools>
|
||||||
|
#include <cstrike>
|
||||||
|
|
||||||
|
#define MAX_TIERS 32
|
||||||
|
|
||||||
|
ConVar g_hCount;
|
||||||
|
ConVar g_hDelay;
|
||||||
|
ConVar g_hUseTiers;
|
||||||
|
ArrayList g_hNames;
|
||||||
|
|
||||||
|
int g_iTierThreshold[MAX_TIERS]; // real player count that triggers this tier
|
||||||
|
int g_iTierMaxBots[MAX_TIERS]; // max bots allowed for this tier
|
||||||
|
int g_iTierCount = 0;
|
||||||
|
int g_iPendingBots = 0;
|
||||||
|
|
||||||
|
bool g_bUseTiers = false;
|
||||||
|
|
||||||
|
public Plugin myinfo = {
|
||||||
|
name = "FakeClients",
|
||||||
|
author = "Tsunami, .Rushaway",
|
||||||
|
description = "Put fake clients in server with tier system",
|
||||||
|
version = "3.0.0",
|
||||||
|
url = "https://github.com/srcdslab/sm-plugin-FakeClients"
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPluginStart()
|
||||||
|
{
|
||||||
|
g_hCount = CreateConVar("sm_fakeclients_players", "8", "Fallback: number of bots when tier system is disabled", _, true, 0.0, true, 64.0);
|
||||||
|
g_hDelay = CreateConVar("sm_fakeclients_delay", "120", "Delay after map change before fake clients join (seconds)", _, true, 0.0, true, 10000.0);
|
||||||
|
g_hUseTiers = CreateConVar("sm_fakeclients_tiers", "0", "Use tier system from fakeclients_tiers.cfg (1 = enabled, 0 = disabled)", _, true, 0.0, true, 1.0);
|
||||||
|
|
||||||
|
g_bUseTiers = g_hUseTiers.BoolValue;
|
||||||
|
g_hUseTiers.AddChangeHook(OnConVarChanged);
|
||||||
|
|
||||||
|
RegAdminCmd("sm_debugfakes", Command_DebugFakes, ADMFLAG_GENERIC, "Shows the amount of fake-clients on server");
|
||||||
|
RegAdminCmd("sm_fakes", Command_Fakes, ADMFLAG_GENERIC, "Shows the fake-clients on server");
|
||||||
|
|
||||||
|
AutoExecConfig(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Command_DebugFakes(int client, int argc)
|
||||||
|
{
|
||||||
|
int iFakes = 0;
|
||||||
|
int iFakesInTeam = 0;
|
||||||
|
int iPlayers = GetClientCount(false);
|
||||||
|
|
||||||
|
for(int i = 1; i <= MaxClients; i++)
|
||||||
|
{
|
||||||
|
if (!IsClientConnected(i))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (IsFakeClient(i) || IsClientSourceTV(i))
|
||||||
|
{
|
||||||
|
iFakes++;
|
||||||
|
iPlayers--;
|
||||||
|
}
|
||||||
|
if (IsFakeClient(i) && GetClientTeam(i) > CS_TEAM_SPECTATOR)
|
||||||
|
iFakesInTeam++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplyToCommand(client, "[SM] There are currently %d Fake-Clients, from which %d are in Spectate. Real Clients %d", iFakes, iFakes - iFakesInTeam, iPlayers);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
// Purpose:
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
public Action Command_Fakes(int client, int args)
|
||||||
|
{
|
||||||
|
char aBuf[1024];
|
||||||
|
char aBuf2[MAX_NAME_LENGTH];
|
||||||
|
|
||||||
|
for(int i = 1; i <= MaxClients; i++)
|
||||||
|
{
|
||||||
|
if(IsClientInGame(i))
|
||||||
|
{
|
||||||
|
if(IsClientConnected(i) && IsFakeClient(i) && !IsClientSourceTV(i))
|
||||||
|
{
|
||||||
|
GetClientName(i, aBuf2, sizeof(aBuf2));
|
||||||
|
StrCat(aBuf, sizeof(aBuf), aBuf2);
|
||||||
|
StrCat(aBuf, sizeof(aBuf), ", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strlen(aBuf))
|
||||||
|
{
|
||||||
|
aBuf[strlen(aBuf) - 2] = 0;
|
||||||
|
ReplyToCommand(client, "[SM] Fake-Clients online: %s", aBuf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ReplyToCommand(client, "[SM] Fake-Clients online: none");
|
||||||
|
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnMapStart()
|
||||||
|
{
|
||||||
|
ParseNames();
|
||||||
|
ParseTiers();
|
||||||
|
CreateTimer(g_hDelay.FloatValue, Timer_CreateFakeClients, _, TIMER_FLAG_NO_MAPCHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnConVarChanged(ConVar hConVar, const char[] sOldValue, const char[] sNewValue)
|
||||||
|
{
|
||||||
|
if (hConVar == g_hUseTiers)
|
||||||
|
{
|
||||||
|
g_bUseTiers = g_hUseTiers.BoolValue;
|
||||||
|
LogMessage("Tier system %s", g_bUseTiers ? "enabled" : "disabled");
|
||||||
|
|
||||||
|
if (g_bUseTiers)
|
||||||
|
{
|
||||||
|
g_iPendingBots = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i <= MaxClients; i++)
|
||||||
|
{
|
||||||
|
if (!IsClientConnected(i) || !IsClientInGame(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!IsFakeClient(i) || IsClientSourceTV(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
KickClient(i, "Client Disconnect");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a moment for all bots to be kicked before trying to add new ones according to tiers.
|
||||||
|
CreateTimer(1.0, Timer_CreateFakeClients, _, TIMER_FLAG_NO_MAPCHANGE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustFakeClientsToTier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the target bot count based on the current number of real players.
|
||||||
|
* Falls back to sm_fakeclients_players if tiers are disabled or not loaded.
|
||||||
|
*/
|
||||||
|
int GetTargetBotCount(int iRealPlayers)
|
||||||
|
{
|
||||||
|
if (!g_bUseTiers || g_iTierCount == 0)
|
||||||
|
return g_hCount.IntValue;
|
||||||
|
|
||||||
|
// Walk tiers from highest threshold down, pick the first one that applies
|
||||||
|
for (int t = g_iTierCount - 1; t >= 0; t--)
|
||||||
|
{
|
||||||
|
if (iRealPlayers >= g_iTierThreshold[t])
|
||||||
|
return g_iTierMaxBots[t];
|
||||||
|
}
|
||||||
|
|
||||||
|
// No tier matched (e.g. config missing threshold "0")
|
||||||
|
LogMessage("Warning: no tier matched for %d real players, defaulting to 0 bots", iRealPlayers);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the clamped target bot count, accounting for available slots and
|
||||||
|
* server over-capacity. Shared by AdjustFakeClientsToTier and OnClientPutInServer.
|
||||||
|
*/
|
||||||
|
int ComputeTarget(int iBots, int iRealPlayers, int iReservedSlots)
|
||||||
|
{
|
||||||
|
int iTarget = GetTargetBotCount(iRealPlayers);
|
||||||
|
if (iTarget < 0)
|
||||||
|
iTarget = 0;
|
||||||
|
|
||||||
|
// Bots currently being added already count as occupied slots
|
||||||
|
int iEffectiveBots = iBots + g_iPendingBots;
|
||||||
|
int iFreeSlots = MaxClients - (iRealPlayers + iEffectiveBots + iReservedSlots);
|
||||||
|
|
||||||
|
if (iFreeSlots < 0)
|
||||||
|
{
|
||||||
|
iTarget = iEffectiveBots + iFreeSlots;
|
||||||
|
if (iTarget < 0)
|
||||||
|
iTarget = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int iMaxBotsBySlots = iEffectiveBots + iFreeSlots;
|
||||||
|
if (iTarget > iMaxBotsBySlots)
|
||||||
|
iTarget = iMaxBotsBySlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kicks excess bots until the bot count reaches iTarget.
|
||||||
|
* Never kicks real players or SourceTV.
|
||||||
|
*/
|
||||||
|
void KickExcessBots(int iBots, int iRealPlayers, int iTarget)
|
||||||
|
{
|
||||||
|
int iToKick = iBots - iTarget;
|
||||||
|
int iKicked = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i <= MaxClients && iKicked < iToKick; i++)
|
||||||
|
{
|
||||||
|
if (!IsClientConnected(i) || !IsClientInGame(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Safety: never kick real players or SourceTV
|
||||||
|
if (!IsFakeClient(i) || IsClientSourceTV(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LogMessage("Kicking fake client '%N' (slot %d) — bots:%d target:%d", i, i, iBots, iTarget);
|
||||||
|
|
||||||
|
KickClient(i, "Client Disconnect");
|
||||||
|
iKicked++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iKicked > 0)
|
||||||
|
LogMessage("Kicked %d bot(s) — real:%d bots:%d target:%d slots:%d", iKicked, iRealPlayers, iBots, iTarget, MaxClients);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules staggered timers to add bots up to iToAdd, respecting free slots.
|
||||||
|
*/
|
||||||
|
void ScheduleBotsToAdd(int iToAdd, int iFreeSlots)
|
||||||
|
{
|
||||||
|
if (iFreeSlots <= 0 || iToAdd <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (iToAdd > iFreeSlots)
|
||||||
|
iToAdd = iFreeSlots;
|
||||||
|
|
||||||
|
// Keep a staggered cadence but add jitter so joins look less scripted.
|
||||||
|
float fNextDelay = GetRandomFloat(0.4, 1.2);
|
||||||
|
|
||||||
|
for (int j = 0; j < iToAdd; j++)
|
||||||
|
{
|
||||||
|
g_iPendingBots++;
|
||||||
|
CreateTimer(fNextDelay, Timer_CreateFakeClient, _, TIMER_FLAG_NO_MAPCHANGE);
|
||||||
|
fNextDelay += GetRandomFloat(0.7, 1.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares current bot count against the target and either kicks excess bots
|
||||||
|
* or schedules new ones to fill the gap.
|
||||||
|
*/
|
||||||
|
void AdjustFakeClientsToTier()
|
||||||
|
{
|
||||||
|
int iBots, iRealPlayers, iReservedSlots;
|
||||||
|
CollectClientCounts(iBots, iRealPlayers, iReservedSlots);
|
||||||
|
|
||||||
|
int iTarget = ComputeTarget(iBots, iRealPlayers, iReservedSlots);
|
||||||
|
int iFreeSlots = MaxClients - (iRealPlayers + iBots + iReservedSlots);
|
||||||
|
|
||||||
|
// Too many active bots -> kick extras
|
||||||
|
if (iBots > iTarget)
|
||||||
|
{
|
||||||
|
g_iPendingBots = 0;
|
||||||
|
KickExcessBots(iBots, iRealPlayers, iTarget);
|
||||||
|
}
|
||||||
|
// Active + pending bots are below target -> add missing bots
|
||||||
|
else if ((iBots + g_iPendingBots) < iTarget)
|
||||||
|
ScheduleBotsToAdd(iTarget - iBots - g_iPendingBots, iFreeSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects fake bots, real players and reserved slots in a single client loop.
|
||||||
|
*/
|
||||||
|
void CollectClientCounts(int &iBots, int &iRealPlayers, int &iReservedSlots)
|
||||||
|
{
|
||||||
|
iBots = 0;
|
||||||
|
iRealPlayers = 0;
|
||||||
|
bool bHasSourceTV = false;
|
||||||
|
|
||||||
|
for (int i = 1; i <= MaxClients; i++)
|
||||||
|
{
|
||||||
|
if (!IsClientConnected(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (IsClientSourceTV(i))
|
||||||
|
{
|
||||||
|
bHasSourceTV = true;
|
||||||
|
continue; // SourceTV occupies its own slot in MaxClients — do not count it as a bot
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsFakeClient(i))
|
||||||
|
iBots++;
|
||||||
|
else
|
||||||
|
iRealPlayers++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve 1 free slot so a real player can always connect.
|
||||||
|
// When SourceTV is active, reserve 1 extra (SourceTV's slot is already in MaxClients).
|
||||||
|
iReservedSlots = bHasSourceTV ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnClientPutInServer(int client)
|
||||||
|
{
|
||||||
|
// Skip fake clients: bot additions are managed via timers.
|
||||||
|
// Calling AdjustFakeClientsToTier on every bot join would cause cascading timers.
|
||||||
|
if (!client || IsFakeClient(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// A real player joined: only kick excess bots, never schedule additions.
|
||||||
|
// Scheduling here would race with the staggered timers already in flight.
|
||||||
|
int iBots, iRealPlayers, iReservedSlots;
|
||||||
|
CollectClientCounts(iBots, iRealPlayers, iReservedSlots);
|
||||||
|
|
||||||
|
int iTarget = ComputeTarget(iBots, iRealPlayers, iReservedSlots);
|
||||||
|
if (iBots > iTarget)
|
||||||
|
KickExcessBots(iBots, iRealPlayers, iTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnClientDisconnect(int client)
|
||||||
|
{
|
||||||
|
// Ignore bot disconnects (caused by our own kicks) to avoid cascading timers.
|
||||||
|
if (IsFakeClient(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CreateTimer(0.5, Timer_CreateFakeClients, _, TIMER_FLAG_NO_MAPCHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Timer_CreateFakeClient(Handle timer)
|
||||||
|
{
|
||||||
|
g_iPendingBots--;
|
||||||
|
if (g_iPendingBots < 0)
|
||||||
|
g_iPendingBots = 0;
|
||||||
|
|
||||||
|
int iBots, iRealPlayers, iReservedSlots;
|
||||||
|
CollectClientCounts(iBots, iRealPlayers, iReservedSlots);
|
||||||
|
|
||||||
|
int iTarget = ComputeTarget(iBots, iRealPlayers, iReservedSlots);
|
||||||
|
int iFreeSlots = MaxClients - (iRealPlayers + iBots + iReservedSlots);
|
||||||
|
|
||||||
|
// Recheck: tier may have changed since this timer was scheduled
|
||||||
|
if (iFreeSlots <= 0 || iBots >= iTarget)
|
||||||
|
return Plugin_Handled;
|
||||||
|
|
||||||
|
char sName[MAX_NAME_LENGTH];
|
||||||
|
char sTarget[MAX_TARGET_LENGTH];
|
||||||
|
int iTargets[MAXPLAYERS];
|
||||||
|
bool bTN_Is_ML;
|
||||||
|
|
||||||
|
// Pick a random name, re-roll if it's already taken by a fake client.
|
||||||
|
// Stop after trying all available names to prevent an infinite loop when
|
||||||
|
// the name list is smaller than the number of bots (duplicates are allowed).
|
||||||
|
// If no names are configured, CreateFakeClient will assign a default name based on the engine.
|
||||||
|
int iNameCount = g_hNames.Length;
|
||||||
|
if (iNameCount > 0)
|
||||||
|
{
|
||||||
|
g_hNames.GetString(GetRandomInt(0, iNameCount - 1), sName, sizeof(sName));
|
||||||
|
|
||||||
|
for (int iAttempt = 1; iAttempt < iNameCount; iAttempt++)
|
||||||
|
{
|
||||||
|
if (ProcessTargetString(sName, 0, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_MULTI, sTarget, MAX_TARGET_LENGTH, bTN_Is_ML) != 1 || !IsFakeClient(iTargets[0]))
|
||||||
|
break;
|
||||||
|
|
||||||
|
g_hNames.GetString(GetRandomInt(0, iNameCount - 1), sName, sizeof(sName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateFakeClient(sName);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Timer_CreateFakeClients(Handle timer)
|
||||||
|
{
|
||||||
|
AdjustFakeClientsToTier();
|
||||||
|
return Plugin_Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads tier thresholds from configs/fakeclients_tiers.cfg. Max 32 tiers.
|
||||||
|
* Format (KeyValues):
|
||||||
|
*
|
||||||
|
* "FakeClientsTiers"
|
||||||
|
* {
|
||||||
|
* "0" "12" // 0 real players → up to 12 bots
|
||||||
|
* "5" "10" // 5+ real players → up to 10 bots
|
||||||
|
* "15" "8" // And so on...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Entries are sorted by threshold automatically, so order in the file
|
||||||
|
* does not matter.
|
||||||
|
*/
|
||||||
|
stock void ParseTiers()
|
||||||
|
{
|
||||||
|
if (!g_bUseTiers)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_iTierCount = 0;
|
||||||
|
|
||||||
|
char sPath[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, sPath, sizeof(sPath), "configs/fakeclients_tiers.cfg");
|
||||||
|
|
||||||
|
KeyValues kv = new KeyValues("FakeClientsTiers");
|
||||||
|
|
||||||
|
if (!kv.ImportFromFile(sPath))
|
||||||
|
{
|
||||||
|
LogError("configs/fakeclients_tiers.cfg not found — falling back to sm_fakeclients_players");
|
||||||
|
g_hUseTiers.BoolValue = false;
|
||||||
|
delete kv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kv.GotoFirstSubKey(false))
|
||||||
|
{
|
||||||
|
LogError("configs/fakeclients_tiers.cfg is empty or malformed");
|
||||||
|
g_hUseTiers.BoolValue = false;
|
||||||
|
delete kv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (g_iTierCount >= MAX_TIERS)
|
||||||
|
break;
|
||||||
|
|
||||||
|
char sKey[16], sVal[16];
|
||||||
|
kv.GetSectionName(sKey, sizeof(sKey));
|
||||||
|
kv.GetString(NULL_STRING, sVal, sizeof(sVal), "-1");
|
||||||
|
|
||||||
|
int iMaxBots = StringToInt(sVal);
|
||||||
|
if (iMaxBots < 0)
|
||||||
|
continue; // skip malformed lines
|
||||||
|
|
||||||
|
g_iTierThreshold[g_iTierCount] = StringToInt(sKey);
|
||||||
|
g_iTierMaxBots[g_iTierCount] = iMaxBots;
|
||||||
|
g_iTierCount++;
|
||||||
|
|
||||||
|
} while (kv.GotoNextKey(false));
|
||||||
|
|
||||||
|
delete kv;
|
||||||
|
|
||||||
|
// Bubble sort tiers by threshold (ascending) so GetTargetBotCount() works correctly
|
||||||
|
for (int i = 0; i < g_iTierCount - 1; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < g_iTierCount - 1 - i; j++)
|
||||||
|
{
|
||||||
|
if (g_iTierThreshold[j] > g_iTierThreshold[j + 1])
|
||||||
|
{
|
||||||
|
int tmp;
|
||||||
|
tmp = g_iTierThreshold[j]; g_iTierThreshold[j] = g_iTierThreshold[j + 1]; g_iTierThreshold[j + 1] = tmp;
|
||||||
|
tmp = g_iTierMaxBots[j]; g_iTierMaxBots[j] = g_iTierMaxBots[j + 1]; g_iTierMaxBots[j + 1] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMessage("Loaded %d tier(s) from fakeclients_tiers.cfg", g_iTierCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads bot display names from configs/fakeclients.txt (one name per line).
|
||||||
|
*/
|
||||||
|
stock void ParseNames()
|
||||||
|
{
|
||||||
|
delete g_hNames;
|
||||||
|
g_hNames = new ArrayList(MAX_NAME_LENGTH);
|
||||||
|
|
||||||
|
char sBuffer[256];
|
||||||
|
BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "configs/fakeclients.txt");
|
||||||
|
|
||||||
|
File hConfig = OpenFile(sBuffer, "r");
|
||||||
|
if (!hConfig)
|
||||||
|
{
|
||||||
|
LogError("configs/fakeclients.txt not found — using default engine name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (hConfig.ReadLine(sBuffer, sizeof(sBuffer)))
|
||||||
|
{
|
||||||
|
TrimString(sBuffer);
|
||||||
|
if (strlen(sBuffer) > 0)
|
||||||
|
g_hNames.PushString(sBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete hConfig;
|
||||||
|
|
||||||
|
if (!g_hNames.Length)
|
||||||
|
LogError("configs/fakeclients.txt is empty — using default engine name");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user