#include <amxmodx>
#include <amxmisc>
#include <fakemeta>
 
new const FAKEPINGS_FILE[] = "kamupingek.ini"
const TASK_ARGUMENTS = 100
 
new cvar_enable, cvar_ping, cvar_flux, cvar_target, cvar_bots, cvar_multiplier, cvar_fileonly, cvar_showactivity
new g_maxplayers, g_connected[33], g_isbot[33], g_argping[33]
new g_loaded_counter, g_pingoverride[33] = { -1, ... }
new Array:g_loaded_authid, Array:g_loaded_ping
 
public plugin_init()
{
	register_plugin("Kamu Ping Mutatas", "1.5a", "MeRcyLeZZ")
 
	cvar_enable = register_cvar("pingfake_enable", "1")
	cvar_ping = register_cvar("pingfake_ping", "1337")
	cvar_flux = register_cvar("pingfake_flux", "0")
	cvar_target = register_cvar("pingfake_target", "1")
	cvar_bots = register_cvar("pingfake_bots", "0")
	cvar_multiplier = register_cvar("pingfake_multiplier", "0.0")
	cvar_fileonly = register_cvar("pingfake_fileonly", "0")
	cvar_showactivity = get_cvar_pointer("amx_show_activity")
 
	g_maxplayers = get_maxplayers()
 
	// If mod is CS, register some additional events to fix a bug
	new mymod[16]
	get_modname(mymod, charsmax(mymod))
	if (equal(mymod, "cstrike") || equal(mymod, "czero"))
	{
		register_event("DeathMsg", "fix_fake_pings", "a")
		register_event("TeamInfo", "fix_fake_pings", "a")
	}
 
	register_forward(FM_UpdateClientData, "fw_UpdateClientData")
 
	register_concmd("amx_fakeping", "cmd_fakeping", ADMIN_KICK, "<target> <ping> - Toggle fake ping override on player (-1 to disable)")
 
	g_loaded_authid = ArrayCreate(32, 1)
	g_loaded_ping = ArrayCreate(1, 1)
 
	// Load list of IP/SteamIDs to fake pings for
	load_pings_from_file()
 
	// Calculate weird argument values regularly in case we are faking ping fluctuations or a multiple of the real ping
	set_task(2.0, "calculate_arguments", TASK_ARGUMENTS, _, _, "b")
}
 
// After some events in CS, the fake pings are overriden for some reason, so we have to send them again...
public fix_fake_pings()
{
	static player
	for (player = 1; player <= g_maxplayers; player++)
	{
		// Player not in game?
		if (!g_connected[player])
			 continue;
 
		// Resend fake pings
		fw_UpdateClientData(player)
	}
}
 
public client_authorized(id)
{
	check_for_loaded_pings(id)
}
 
public client_putinserver(id)
{
	g_connected[id] = true
	if (is_user_bot(id)) g_isbot[id] = true
	check_for_loaded_pings(id)
}
 
public client_disconnect(id)
{
	g_connected[id] = false
	g_isbot[id] = false
	g_pingoverride[id] = -1
}
 
public fw_UpdateClientData(id)
{
	// Ping faking disabled?
	if (!get_pcvar_num(cvar_enable)) return;
 
	// Scoreboard key being pressed?
	if (!(pev(id, pev_button) & IN_SCORE) && !(pev(id, pev_oldbuttons) & IN_SCORE))
		return;
 
	// Send fake player's pings
	static player, sending, bits, bits_added
	sending = false
	bits = 0
	bits_added = 0
 
	for (player = 1; player <= g_maxplayers; player++)
	{
		// Player not in game?
		if (!g_connected[player])
			 continue;
 
		// Fake latency for its target too?
		if (!get_pcvar_num(cvar_target) && id == player)
			continue;
 
		// Fake pings enabled for players on .INI file ONLY and this guy is not listed
		if (get_pcvar_num(cvar_fileonly) && g_pingoverride[player] < 0)
			continue;
 
		// Only do these checks if not overriding ping for player
		if (g_pingoverride[player] < 0)
		{
			// Is this a bot?
			if (g_isbot[player])
			{
				// Bots setting disabled?
				if (!get_pcvar_num(cvar_bots)) continue;
			}
			else
			{
				// Bots only setting?
				if (get_pcvar_num(cvar_bots) == 2) continue;
			}
		}
 
		// Start message
		if (!sending)
		{
			message_begin(MSG_ONE_UNRELIABLE, SVC_PINGS, _, id)
			sending = true
		}
 
		// Add bits for this player
		AddBits(bits, bits_added, 1, 1) // flag = 1
		AddBits(bits, bits_added, player-1, 5) // player-1 since HL uses ids 0-31
		AddBits(bits, bits_added, g_argping[player], 12) // ping
		AddBits(bits, bits_added, 0, 7) // loss
 
		// Write group of 8 bits (bytes)
		WriteBytes(bits, bits_added, false)
	}
 
	// End message
	if (sending)
	{
		// Add empty bit at the end
		AddBits(bits, bits_added, 0, 1) // flag = 0
 
		// Write remaining bits
		WriteBytes(bits, bits_added, true)
 
		message_end()
	}
}
 
public cmd_fakeping(id, level, cid)
{
	// Check for access flag
	if (!cmd_access(id, level, cid, 3))
		return PLUGIN_HANDLED;
 
	// Retrieve arguments
	static arg[32], player, ping
	read_argv(1, arg, sizeof arg - 1)
	player = cmd_target(id, arg, CMDTARGET_ALLOW_SELF)
	read_argv(2, arg, sizeof arg - 1)
	ping = str_to_num(arg)
 
	// Invalid target
	if (!player) return PLUGIN_HANDLED;
 
	// Update ping overrides for player
	g_pingoverride[player] = min(ping, 4095)
 
	// Get player's name for displaying/logging activity
	static name1[32], name2[32]
	get_user_name(id, name1, sizeof name1 - 1)
	get_user_name(player, name2, sizeof name2 - 1)
 
	// Negative value means disable fakeping
	if (ping < 0)
	{
		// Show activity?
		switch (get_pcvar_num(cvar_showactivity))
		{
			case 1: client_print(0, print_chat, "ADMIN - fake ping override disabled on %s", name2)
			case 2: client_print(0, print_chat, "ADMIN %s - fake ping override disabled on %s", name1, name2)
		}
 
		// Log activity
		static logdata[100], authid[32], ip[16]
		get_user_authid(id, authid, sizeof authid - 1)
		get_user_ip(id, ip, sizeof ip - 1, 1)
		formatex(logdata, sizeof logdata - 1, "ADMIN %s <%s><%s> - fake ping override disabled on %s", name1, authid, ip, name2)
		log_amx(logdata)
	}
	else
	{
		// Show activity?
		switch (get_pcvar_num(cvar_showactivity))
		{
			case 1: client_print(0, print_chat, "ADMIN - fake ping override of %d enabled on %s", ping, name2)
			case 2: client_print(0, print_chat, "ADMIN %s - fake ping override of %d enabled on %s", name1, ping, name2)
		}
 
		// Log activity
		static logdata[100], authid[32], ip[16]
		get_user_authid(id, authid, sizeof authid - 1)
		get_user_ip(id, ip, sizeof ip - 1, 1)
		formatex(logdata, sizeof logdata - 1, "ADMIN %s <%s><%s> - fake ping override of %d enabled on %s", name1, authid, ip, ping, name2)
		log_amx(logdata)
	}
 
	return PLUGIN_HANDLED;
}
 
// Calculate argument values based on target ping
public calculate_arguments()
{
	static player, ping, loss
	for (player = 1; player <= g_maxplayers; player++)
	{
		// Calculate target ping (clamp if out of bounds)
		if (g_pingoverride[player] < 0)
		{
			if (get_pcvar_float(cvar_multiplier) > 0.0)
			{
				get_user_ping(player, ping, loss)
				g_argping[player] = clamp(floatround(ping * get_pcvar_float(cvar_multiplier)), 0, 4095)
			}
			else
				g_argping[player] = clamp(get_pcvar_num(cvar_ping) + random_num(-abs(get_pcvar_num(cvar_flux)), abs(get_pcvar_num(cvar_flux))), 0, 4095)
		}
		else
			g_argping[player] = g_pingoverride[player]
	}
}
 
load_pings_from_file()
{
	// Build file path
	new path[64]
	get_configsdir(path, sizeof path - 1)
	format(path, sizeof path - 1, "%s/%s", path, FAKEPINGS_FILE)
 
	// File not present, skip loading
	if (!file_exists(path)) return;
 
	// Open file for reading
	new linedata[40], authid[32], ping[8], file = fopen(path, "rt")
 
	while (file && !feof(file))
	{
		// Read one line at a time
		fgets(file, linedata, sizeof linedata - 1)
 
		// Replace newlines with a null character to prevent headaches
		replace(linedata, sizeof linedata - 1, "^n", "")
 
		// Blank line or comment
		if (!linedata[0] || linedata[0] == ';') continue;
 
		// Get authid and ping
		strbreak(linedata, authid, sizeof authid - 1, ping, sizeof ping -1)
		remove_quotes(ping)
 
		// Store data into global arrays
		ArrayPushString(g_loaded_authid, authid)
		ArrayPushCell(g_loaded_ping, clamp(str_to_num(ping), 0, 4095))
 
		// Increase loaded data counter
		g_loaded_counter++
	}
	if (file) fclose(file)
}
 
check_for_loaded_pings(id)
{
	// Nothing to check for
	if (g_loaded_counter <= 0) return;
 
	// Get steamid and ip
	static authid[32], ip[16], i, buffer[32]
	get_user_authid(id, authid, sizeof authid - 1)
	get_user_ip(id, ip, sizeof ip - 1, 1)
 
	for (i = 0; i < g_loaded_counter; i++)
	{
		// Retrieve authid
		ArrayGetString(g_loaded_authid, i, buffer, sizeof buffer - 1)
 
		// Compare it with this player's steamid and ip
		if (equali(buffer, authid) || equal(buffer, ip))
		{
			// We've got a match!
			g_pingoverride[id] = ArrayGetCell(g_loaded_ping, i)
			break;
		}
	}
}
 
AddBits(&bits, &bits_added, value, bit_count)
{
	// No more room (max 32 bits / 1 cell)
	if (bit_count > (32 - bits_added) || bit_count < 1)
		return;
 
	// Clamp value if its too high
	if (value >= (1 << bit_count))
		value = ((1 << bit_count) - 1)
 
	// Add new bits
	bits = bits + (value << bits_added)
	// Increase bits added counter
	bits_added += bit_count
}
 
WriteBytes(&bits, &bits_added, write_remaining)
{
	// Keep looping if there are more bytes to write
	while (bits_added >= 8)
	{
		// Write group of 8 bits
		write_byte(bits & ((1 << 8) - 1))
 
		// Remove bits we just sent by moving all bits to the right 8 times
		bits = bits >> 8
		bits_added -= 8
	}
 
	// Write remaining bits too?
	if (write_remaining && bits_added > 0)
	{
		write_byte(bits)
		bits = 0
		bits_added = 0
	}
}