master 7bd678e2b5a6 cached
42 files
184.5 KB
59.8k tokens
1 requests
Download .txt
Repository: J-Tanzanite/Little-Anti-Cheat
Branch: master
Commit: 7bd678e2b5a6
Files: 42
Total size: 184.5 KB

Directory structure:
gitextract_9cluogkd/

├── .github/
│   └── FUNDING.yml
├── Changelog
├── README.md
├── plugins/
│   └── lilac.smx
├── scripting/
│   ├── include/
│   │   └── convar_class.inc
│   ├── lilac/
│   │   ├── lilac_aimbot.sp
│   │   ├── lilac_aimlock.sp
│   │   ├── lilac_angles.sp
│   │   ├── lilac_anti_duck_delay.sp
│   │   ├── lilac_backtrack.sp
│   │   ├── lilac_bhop.sp
│   │   ├── lilac_config.sp
│   │   ├── lilac_convar.sp
│   │   ├── lilac_database.sp
│   │   ├── lilac_globals.sp
│   │   ├── lilac_lerp.sp
│   │   ├── lilac_macro.sp
│   │   ├── lilac_noisemaker.sp
│   │   ├── lilac_ping.sp
│   │   ├── lilac_stock.sp
│   │   └── lilac_string.sp
│   └── lilac.sp
├── translations/
│   ├── chi/
│   │   └── lilac.phrases.txt
│   ├── cze/
│   │   └── lilac.phrases.txt
│   ├── da/
│   │   └── lilac.phrases.txt
│   ├── de/
│   │   └── lilac.phrases.txt
│   ├── es/
│   │   └── lilac.phrases.txt
│   ├── fi/
│   │   └── lilac.phrases.txt
│   ├── fr/
│   │   └── lilac.phrases.txt
│   ├── hu/
│   │   └── lilac.phrases.txt
│   ├── lilac.phrases.txt
│   ├── lv/
│   │   └── lilac.phrases.txt
│   ├── nl/
│   │   └── lilac.phrases.txt
│   ├── no/
│   │   └── lilac.phrases.txt
│   ├── pl/
│   │   └── lilac.phrases.txt
│   ├── pt/
│   │   └── lilac.phrases.txt
│   ├── ro/
│   │   └── lilac.phrases.txt
│   ├── ru/
│   │   └── lilac.phrases.txt
│   ├── sv/
│   │   └── lilac.phrases.txt
│   ├── tr/
│   │   └── lilac.phrases.txt
│   └── ua/
│       └── lilac.phrases.txt
└── updatefile.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://steamcommunity.com/tradeoffer/new/?partner=883337522&token=D4Ku6oDJ']


================================================
FILE: Changelog
================================================
1.7.4
- Removed the need to download other third party includes (SB, SB++, MA and Updater).
- Removed Angle checks in L4D1&2 (Fix coming in 1.8).
- Added define in lilac.sp for TF2Classic support.
- Added support for the old version of SourceBans.
- Added admin chat warnings for suspicious players (Aimbot, Aimlock and Bhop).
- Removed custom warnings when compiling (some people mistook them for errors).
- Fixed a bug where multiple aimbot checks are fired upon getting multiple kills from the same shot.
- Updated sourcecode to compile in SM 1.11 without warnings.

1.7.3
- Removed a redundant function.
- Added Air-Time check to Bhop detection (Default 0.3 seconds).
- Fixed an issue where Bhop bans don't always log the amount of bhops done.
- Updated default Bhop presets.
- Updated Czech translations, thanks luk27official.

1.7.2
- Added experimental support for TF2Classic (Needs manual recompiling with `TF2C` defined in source).
- Added Finnish translations, by Veeti.
- Removed unused UTF-8 check flags.
- Changed ConVars to use kidfearless's methodmaps version.
- Fixed so aimbot isn't checked on dead players (Should fix grenades in CS:GO).

1.7.1
- Reorganized source code to be in separate files.
- - The plugin itself is still a single file, making installation easier.
- - But this should make code easier to follow.
- Removed "Randomized" Backtrack patch method, as the "Lock" method is better.
- Removed old Bhop detection code.
- - Bhop detection now has pre-defined "configs", and a custom mode.
- - "lilac_bhop 1 & 2" have been disabled, if your config already is using those modes, it will automatically swap to "Medium" mode.
- Added SourceIRC support.
- Added new ConVar "lilac_sourceirc" (Default 1), send logs to SourceIRC.
- Added Database logging.
- Added new ConVar "lilac_database" (Default ""). Send logs to MySQL or SQLite.
- Added new Command (lilac_bhop_set), only available if "lilac_bhop" is set to 3 (custom mode).
- Added compile warnings if Sourcebans++, Material-Admin or Updater includes fail.
- - These are just warnings, not errors.
- Added new ConVar "lilac_ban_language" (Default 1), which language should be used for ban reasons.
- - 1 = Server.
- - 2 = Client.
- Added Counter-Strike:Source to the official supported list of games.
- - People use it and don't report issues.
- Added new Macro ConVar mode ("lilac_macro 2"), allows detecting macros without logging.
- Added new ConVar "lilac_macro" (Default 0), detect macro usage.
- Added new ConVar "lilac_macro_warning" (Default 1).
- - 0 = Disabled.
- - 1 = Warn player.
- - 2 = Warn admins.
- - 3 = Warn everyone.
- Added new ConVar "lilac_macro_method" (Default 0).
- - 0 = Kick.
- - 1 = Ban (Default ban length is 15 minutes, min possible is 15, max is 60).
- Added new ConVar "lilac_macro_mode" (Default 0), what types of macros to detect.
- - 0 = All.
- - 1 = Auto-Jump.
- - 2 = Auto-Shoot.
- Added new ConVar "lilac_filter_name" (Default 2).
- - 0 = Disabled.
- - 1 = Kick only.
- - 2 = Ban cheaters with newlines in name.
- Added new ConVar "lilac_filter_chat" (Default 1).
- - Filters chat for invalid characters, also blocks Bismillah spam.
- Fixed sm_basepath not being respected.
- Fixed ban status message (lilac_ban_status) being spammed.
- Fixed general code ordering to be more efficient.
- Fixed Aimlock detection method being bloated and not running correctly.
- Fixed map teleports causing issues for Aimbot&Aimlock detection and Backtrack patch.
- Fixed false positive for NoLerp on servers which allow any interp ratio (sv_client_min_interp_ratio && sv_client_max_interp_ratio), thanks RoseTheFox!
- Updated default ConVar value of "lilac_noisemaker" to be "1".
- Updated all cheat detections to have a "log only" option.
- - Negative values, example: "lilac_angles -1" will detect Angle-Cheats, but log only.
- - Check ConVar descriptions for more info.
- Updated NoLerp bans to no longer be displayed as ConVar bans.
- - NoLerp bans have their own ban message now, need help updating all translations to reflect this.
- Updated command "lilac_ban_status" to include Lilac's version number.
- Updated outdated coding style.
- Updated German translations, thanks freakexeuLow.
- Updated Norwegian translations, thanks... Me...
- Updated Spanish translations, thanks 4LEJ4NDRO.

1.6.3
- Fixed so SourceTV isn't considered a valid player.

1.6.2 (Never officially released on AM)
- Fixed rare case in TF2 where bumper carts used outside of cart areas in official halloween maps could cause false positives when stood on weird inclines.

1.6.1
- Fixed bug where angle-cheats would ban all players in L4D and L4D2.

1.6.0
 - Removed redundant code.
 - Added new cheat detection feature for CS:GO (Only), Anti-Duck-Delay/FastDuck.
 - Added new BETA (May not work) TF2 cheat detection for Infinite Noisemaker Spam. Since it is in BETA, it WON'T ban, only log! If no false positives are reported, it will perma ban in the future.
 - Added BETA auto updater support.
 - Added new ConVar "lilac_anti_duck_delay" (Default 1), detect Anti-Duck-Delay/FastDuck in CS:GO.
 - Added new ConVar "lilac_noisemaker" (Default 1), detect infinite noisemaker in TF2.
 - Added new ConVar "lilac_auto_update" (Default 0), enable this to auto update (Requires updater plugin).
 - Added new ConVar "lilac_max_ping_spec" (Default 0), moves players with high ping into team spectator and warns them about potential kick after x many seconds.
 - Added Russian warning if MA wasn't included when compiled (command: lilac_ban_status).
 - Added new backtrack patch method, Lock. This patch method shouldn't affect laggy legit players much.
 - Added a delay for forwards so they won't get spammed to other plugins.
 - Fixed overly long ConVar description for "lilac_max_lerp".
 - Fixed typo in max ping ConVar description, Thanks 4LEJ4NDRO/ALEJANDRO!
 - Fixed a typo in code and translations files.
 - Updated Bhop to have a lower chance of false positives, thanks M4rkey and Thundy!
 - Updated Ping kicker to wait 100 seconds before kicking instead of 45 seconds.
 - Updated Ping kicker to skip testing players who have not been in game for more than 120 seconds.
 - Updated default ban length for Bhop to be 1 month instead of permanently, do "lilac_set_ban_length bhop -1" to use the ConVar value "lilac_ban_length" instead.
 - Updated Aimlock to check newly connected players for AimLock.
 - Updated command "lilac_ban_status" to tell you if bans will go through Sourcebans++, Material-Admin or Basebans.
 - Updated command "lilac_ban_status" to show if native ban functions are available.
 - Updated so ban status will be printed after all plugins are loaded along with startup message.
 - Updated how banning works through Sourcebans++/MaterialAdmin, it will now check if the native exists and not if the plugin by name is loaded.
 - Updated ConVar checker to be more basic and less CPU intensive.


1.5.1 (Never officially released on AM)
 - Added new command "lilac_ban_status", which prints to server console the status of Sourcebans++ and Material-Admin.
 - Removed mat_fullbright comparison, despite it having been removed from queries.


1.5.0
 - Added new ConVar "lilac_aimbot_autoshoot" (Default 1), enables autoshoot detection.
 - Added new command "lilac_set_ban_length", can be used to overwrite ban length for specific cheat detections.
 - Added German, Spanish, Portuguese, Turkish and Ukrainian translations.
 - Fixed false Angle-Cheat detections in L4D (Thanks finishlast).
 - Fixed false ConVar detection "mat_fullbright" on some community made maps. Lilac will no longer check for this ConVar.
 - Fixed some errors in Aimlock detections.


1.4.0 (Never officially released on AM)
 - Added support for MateralAdmin (Thanks panikajo and CrazyHackGUT).
 - Added new ConVar "lilac_ban_length" (Default 0), sets ban length in minutes (0 = Forever).


1.3.0
 - Fixed false Angle-Cheat detections in Left4Dead2 (Thanks larrybrains).
 - Updated ConVar "lilac_max_lerp" to be disabled if less than 105.
 - Updated where detection logs are stored, from "{gamefolder}/lilac.log" to "{gamefolder}/addons/sourcemod/logs/lilac.log".


1.2.0 (Never officially released on AM)
 - Added new ConVar "lilac_ban" (Default 1), set to 0 to disable all banning (useful for those who want to test Lilac before fully trusting it).
 - Updated code syntax so older versions of sourcemod can compile and run Lilac.
 - Updated ConVar updates to be cleaner, thanks MAGNAT2645!
 - Updated ConVar checker to not kick unresponsive clients so quickly.
 - Updated the default config location to "cfg/sourcemod", if the old config file is still in the "cfg/" folder, the old file will still be used.


1.1.0 (Never officially released on AM)
 - Added new forward for blocking cheat detections (Should be used by bhop/VIP plugins).
 - Fixed some false positives for Aimlock detections (Hopefully, still not sure what caused issues for others).
 - Fixed aimlock lightweight mode testing 6 players, not 5 (Typos are fun :D).
 - Updated backtrack patch to last 1 second instead of 5 (Laggy players should not get punished so harshly now).


1.0.0
 - Rewrote large portions of the Anti-Cheat (A complete rewrite?).
 - Removed OnGameFrame check in TF2 for taunting players.
 - Added translation support, Lilac now supports French, Russian, Norwegian and English.
 - Added startup message when Little Anti-Cheat is loaded.
 - Added TF2 forward for checking when players are taunting.
 - Added new ConVar "lilac_aimlock_light" (Default 1), if enabled, won't check for aimlocks on all players constantly to prevent lag on some servers.
 - Added new ConVar "lilac_welcome" (Default 0) saying the server is protected.
 - Added new ConVar "lilac_loss_fix" (Default 1), if enabled, ignores some detections on laggy players (packet loss).
 - Added new ConVar "lilac_log_misc" (Default 0), if enabled, lilac will log when players are kicked for misc features (high ping, interp exploit and query failure).
 - Added new forward when players are banned (lilac_cheater_banned(int client, int cheat)).
 - Fixed plugin not loading in CS:GO (Thanks Bottiger).
 - Fixed extreme rare case where aimbot detector would look at the wrong victim.
 - Fixed cases where Lilac would look a little too far back at tick history.
 - Fixed so connecting players can't inherit angle history from previous players.
 - Fixed missing punctuation in NoLerp detection log message.
 - Fixed a bug where aimlock detections would not expire after 10 minutes, but aimbot detections would (Typos are fun).
 - Fixed sourcebans++ compatibility not working (Thanks foon).
 - Fixed so repeat tests (aimbot) aren't done between close players.
 - Updated interp exploit kicker to display the correct interp convar value.
 - Updated several comments and ConVar descriptions to be more clear.
 - Updated Aimlock detector to ignore players who are too close to each other.
 - Updated Aimlock detector to consider packet loss (if lilac_loss_fix is enabled).
 - Updated Aimbot detector to consider packet loss (if lilac_loss_fix is enabled, total_delta detection works regardless).
 - Updated Aimbot detector to check for things it previously wouldn't under certain circumstances.
 - Updated ConVar detector to query for ConVars every 5 seconds instead of every 2 seconds.
 - Updated Backtrack patch to last 5 seconds instead of 10.
 - Updated Backtrack patch to use correct random tick ranging from -200ms to max 200ms based on ping.
 - Updated "lilac_log_extra" to have an option to also log extra information on every detection, suspicions and kick.
 - Updated coding style somewhat, to make it easier to follow and understand.


0.7.1
 - Fixed potential for false NoLerp ban if sv_maxupdaterate is updated mid-game and then plugin is loaded.
 - Changed high ping players getting kicked after 100 seconds to 45 seconds.
 - Changed Aimlock detection to increment after two snaps instead of three.
 - Changed so cheaters banned for Chat-Clear can't continue spamming chat.
 - Removed "Full" backtrack patch method, it was never used anyway (Old stuff from development/testing).
 - Changed backtrack patch to modify tickcount to a random value ranging from 400ms instead of 200ms.


================================================
FILE: README.md
================================================
# Closing notes
I wish to thank everyone who participated in this project, it's been an interesting ride.\
As fun as it has been, I think it's time to make it official: I quit.

As some can tell, I've become fairly inactive as of late,\
as fun and interesting as this project initially was to me years ago,\
I simply don't wish to work on it anymore. Frankly, games don't interest me anymore, I've moved on.

Originally, I was planning on handing this repo over to someone else to maintain,\
and handing them some of the private detection methods I developed years ago,\
and while I did start that process, I've come to change my mind.

It would be a decision that the users of Lilac had no say in,\
and given that the surface area of attack grows the more people are brought on,\
I decided it would just be best to leave this repo as it is.\
Thus, I've decided to just archive this project.

I've done my fair share, and fixed some wrongs.\
At the end of the day, all projects come to an end at some point,\
and I think this project has served its purpose, at least as far as I've cared to carry it.

## Going forward
If you are still in dire need of an anti-cheat for TF2, look to: github.com/sapphonie/StAC-tf2

While it's not perfect (nothing is), and we have some ideological differences in how we write code - their project is good.\
On the other hand, if you still wish to use Lilac, find a fork of the project that is active by people you trust.\
Or make one yourself, it's easier than you think.

As for the private detection methods?\
I wouldn't worry about it, developers are creative and will figure it out,\
and they'll figure new patterns out too.

Thanks for everything, and farewell.

# Little Anti-Cheat

Little Anti-Cheat is a free and open source anti-cheat for source games, and runs on SourceMod.\
It was originally developed for some secret servers I had back in the day.\
But, as I quit cheating and quit having servers, I decided to release this project to help the community out.\
This Anti-Cheat is by no means perfect, and it is bypassable to some extent, but it should still be helpful in dealing with cheaters :)

### Current Cheat Detections:
 - Angle-Cheats (Legit Anti-Backstab (TF2), Basic Anti-Aims and Duckspeed).
 - Chat-Clear (When cheaters clear the chat).
 - Basic Invalid ConVar Detector (Checks if clients have sv_cheats turned on and such).
 - BunnyHop (Bhop).
 - Basic Projectile and Hitscan Aimbot.
 - Basic Aimlock.
 - Anti-Duck-Delay/FastDuck (CS:GO only).
 - Newlines in names.

### Misc features:
 - Angle-Cheats Patch (Patches Angle-Cheats from working).
 - Max Interp Kicker (Kicks players for attempting to exploit interp (cl_interp 0.5)).
 - Max Ping Kicker (Kicks players for having too high ping (Disabled by default)).
 - Backtrack Patch (Patches backtrack cheats (Disabled by default)).
 - Macro detection.
 - Invalid name detection.
 - Invalid characters in chat patch (+ chat clear exploit fix).

### Supported Games:
 - [TF2] Team Fortress 2
 - [CS:GO] Counter-Strike:Global Offensive
 - [CS:S] Counter-Strike:Source
 - [L4D2] Left 4 Dead 2
 - [L4D] Left 4 Dead
 - [DoD:S] Day of Defeat: Source

### Untested, but should work in:
 - [HL2:DM] Half-Life 2:DeathMatch

## FAQ
**Q: What is Autoshoot?**\
A: Autoshoot is when a cheat fires a perfect 1-tick shot.\
It's quite common for cheats to do this when using aimbot.\
Autoshoot detections work by detecting 1-tick perfect shots that lead to a kill twice in a row (Autoshoot will get logged if another aimbot type was detected tho).

You *can* get a false positive for Autoshoot, but that should be very rare.\
It is possible to trigger a false positive if you use "bind mwheeldown/up +attack", as scroll (for some reason) does perfect 1-tick input.\
That said, if someone has to go out of their way to do something stupid and abnormal to get a ban, they've basically asked for it.\
If this is a problem for you, you can set `lilac_aimbot_autoshoot` to `0`.

Important thing to note about Autoshoot, because this feature shoots for you, you cannot tell if someone is using Autoshoot by spectating them, or through STV demos. Autoshoot isn't visible in demos or for spectators.

**Q: What is Anti-Duck-Delay? There are so many bans for it, are they false positives?**\
A: Are they false positives? In short: **No.**\
Anti-Duck-Delay (Most commonly called **FastDuck**) is a cheat feature in CS:GO that is available in a LOT of cheats.\
In fact, Anti-Duck-Delay is so commonly used by cheaters, that most bans issued by Lilac in CS:GO will be for this.\
Anti-Duck-Delay works by inputting a value into your usercmd buttons, that is impossible to input by legit players; only internal cheats can do this.

I understand if this makes you anxious, since there are a LOT of bans for ADD, but this is completely normal.\
If someone gets banned for this, they were cheating.

**Q: What is NoLerp?**\
A: "NoLerp" is when cheats set their interpolation to 0ms (or lower than the minimum possible).\
This is often done to increase their Aimbot accuracy.

**Q: What are Angle-Cheats?**\
A: Angle-Cheats is when a player's view angles are set beyond the limits of the game.\
This is often done to create a desync between their model and hitbox, making it harder to shoot them.\
It can also be done to execute some other exploits; like in TF2 with Duckspeed.

Note: Lilac currently does not check for yaw, so some desyncs are still possible and not detected.

**Q: Are Macros cheats?**\
A: No.\
Macros are just when a player is using a script to input buttons for them (AutoHotKey for instance), or by using scroll to spam some input.\
This is why Macro detections can only ban for 15 to 60 minutes, and no more.\
Macro detections are by default disabled, because most servers don't care about this.

**Q: Does Lilac ban for high ping?**\
A: Not quite.\
The optional high ping kicker (which is disabled by default) in Lilac bans players for 3 minutes, after that, they can reconnect.\
The reason for this is simple, if you only kicked high ping players, they could instantly reconnect.

## Non-Steam versions / CS:S v34 / CS:S v91 / ETC...
Non-Steam versions (IE: Cracks) **ARE NOT SUPPORTED!**\
I am sorry to say, but non-steam versions aren't supported.\
This is because of technical problems with cracks, as they tend to be of older versions of the game, which means they'll have bugs that can conflict with some cheat detections.\
And I just don't want to support piracy.\
I also just don't want to download sketchy unofficial cracked versions of games...

So Little Anti-Cheat may not work out of the box for cracked versions of games.\
That said, I've decided to be a little helpful based on feedback from others.

For Non-Steam/Cracked version of CS:S (like v34 or v91), Angle-Cheat detections won't work.\
You can fix this by updating these ConVars: `lilac_angles 0` and `lilac_angles_patch 0`.\
These **HAVE** to be disabled.

### Credits / Special Thanks to:
 - J_Tanzanite... Yeah I'm crediting myself for writing this AC...
 - Azalty, for being (rightly) stubborn regarding an issue and for contributing database logging.
 - foon, for fixing sourcebans++ not working (https://forums.alliedmods.net/showthread.php?p=2689297#post2689297).
 - Bottiger, for fixing this plugin not working in CS:GO and general criticisms.
 - MAGNAT2645 for suggesting a cleaner method of handling convar changes.
 - Larry/LarryBrains for informing me of false Angle-Cheat detections in L4D2.
 - VintagePC (https://github.com/vintagepc) for SourceIRC support and basepath fix.

### Current languages supported:
 - Simplified Chinese (by RoyZ https://github.com/RoyZ-CSGO ^-^, and apples194)
 - Dutch (by snowy UwU OwO EwE).
 - Danish (by kS the Man / ksgoescoding c:).
 - Norwegian (by me, the translations could be better).
 - French (by Rasi / GreenGuyRasi).
 - Finnish (By [Veeti](https://forums.alliedmods.net/member.php?u=317665)).
 - English (by me lol duh hue hue hue).
 - Russian (by an awesome person c:).
 - Czech (by luk27official and someone else).
 - Brazilian Portuguese by SheepyChris (https://github.com/SheepyChris), Tiagoquix (https://github.com/Tiagoquix) and Crashzk (https://github.com/crashzk).
 - German (by two humble nice Germans c:).
 - Spanish (by ALEJANDRO ^-^).
 - Ukrainian (by panikajo ;D).
 - Polish (by https://github.com/qawery-just-sad).
 - Turkish (by ShiroNje and R3nzTheCodeGOD).
 - Hungarian (by The Solid Lad).
 - Swedish (by Teamkiller324).
 - Latvian (by rcon420).
 - Romanian (by rigE08).


I do hope to add more languages in the future.\
But at least you can add or improve on the translations already provided.\
My friends who did some of the translations were told by me that the translations don't have to be perfect.\
Just understandable to those who don't speak English too well.

### Optional:
 - Sourcebans++
 - MaterialAdmin
 - Updater


================================================
FILE: scripting/include/convar_class.inc
================================================
#if defined _convar_class_included
 #endinput
#endif
#define _convar_class_included

// todo: track previous default values 

static ArrayList _ConvarList;

enum struct convar_t
{
	ConVar cvar;
	char description[512];
	char defValue[64];
	char name[255];

	bool GetMin(float& input)
	{
		return this.cvar.GetBounds(ConVarBound_Lower, input);
	}
	bool GetMax(float& input)
	{
		return this.cvar.GetBounds(ConVarBound_Upper, input);
	}
	void SetMin(bool set, float& input = 0.0)
	{
		this.cvar.SetBounds(ConVarBound_Lower, set, input);
	}
	void SetMax(bool set, float& input = 0.0)
	{
		this.cvar.SetBounds(ConVarBound_Upper, set, input);
	}
}

methodmap Convar < ConVar
{
	public Convar(const char[] name, const char[] defaultValue, const char[] description = "",
					 int flags = 0, bool hasMin = false, float min = 0.0, bool hasMax = false, float max = 0.0, convar_t convar = {})
	{
		if(_ConvarList == null)
		{
			_ConvarList = new ArrayList(sizeof(convar_t));
		}

		ConVar cvar = CreateConVar(name, defaultValue, description, flags, hasMin, min, hasMax, max);

		convar_t savedValue;
		savedValue.cvar = cvar;
		strcopy(savedValue.description, 512, description);
		strcopy(savedValue.defValue, 64, defaultValue);
		
		// Can't set default values :T
		savedValue.SetMin(hasMin, min);
		savedValue.SetMax(hasMax, max);

		convar = savedValue;

		_ConvarList.PushArray(savedValue);

		return view_as<Convar>(cvar);
	}

	public static bool CreateConfig(const char[] fileName = "", const char[] folder = "sourcemod", const bool clearWhenDone = true)
	{
		char localFolder[PLATFORM_MAX_PATH];
		FormatEx(localFolder, PLATFORM_MAX_PATH, "cfg/%s", folder);
		if(!DirExists(localFolder))
		{
			if(!CreateDirectory(localFolder, 755))
			{
				LogError("Error: Failed to create folder '%s'", localFolder);
			}
		}

		if(_ConvarList == null)
		{
			LogError("Error: No convars found. did you run .CreateConfig() before adding convars?");
			return false;
		}


		// Check if the file exists.
		char local[PLATFORM_MAX_PATH];
		if(fileName[0] == 0)
		{
			char pluginName[PLATFORM_MAX_PATH];
			GetPluginFilename(GetMyHandle(), pluginName, PLATFORM_MAX_PATH);

			ReplaceString(pluginName, PLATFORM_MAX_PATH, ".smx", "");

			int start = FindCharInString(pluginName, '/', true);

			FormatEx(local, PLATFORM_MAX_PATH, "cfg/%s/plugin.%s.cfg", folder, pluginName[start+1]);
		}
		else
		{
			FormatEx(local, sizeof(local), "cfg/%s/%s.cfg", folder, fileName);
		}
		bool fileExists = FileExists(local);
		if (!fileExists)
		{
			// Create first time file
			File file = OpenFile(local, "wt");
			if (file != null)
			{
				fileExists = true;

				// get the plugin name
				char pluginName[64];
				GetPluginFilename(GetMyHandle(), pluginName, 64);

				// Write warning
				file.WriteLine("// This file was auto-generated by KiD's Convar Class. Only plugin convars are allowed.");
				file.WriteLine("// ConVars for plugin \"%s\"\n\n", pluginName);

				// Loop through all of our convars
				for (int i = 0; i < _ConvarList.Length; ++i)
				{
					// get the current convar and description
					convar_t convar;
					_ConvarList.GetArray(i, convar);

					// don't write to file if flag is set
					if (convar.cvar.Flags & FCVAR_DONTRECORD != 0)
					{
						continue;
					}
					
					// make a copy of our description
					char descr[512];
					descr = convar.description;

					// format newlines as comments
					ReplaceString(descr, 512, "\n", "\n// ");

					// write the values and bounds to the file if they exist
					file.WriteLine("// %s", descr);

					// get the convar name and default value to write to file
					char name[64];
					convar.cvar.GetName(name, 64);

					file.WriteLine("// -");
					file.WriteLine("// Default: \"%s\"", convar.defValue);
					float x;
					if (convar.GetMin(x))
					{
						file.WriteLine("// Minimum: \"%02f\"", x);
					}
					if (convar.GetMax(x))
					{
						file.WriteLine("// Maximum: \"%02f\"", x);
					}
					file.WriteLine("%s \"%s\"\n", name, convar.defValue);
				}
				// end with newline
				file.WriteLine("");
				delete file;
			}
			else
			{
				// writing failed, notify developer.
				char pluginName[64];
				GetPluginFilename(GetMyHandle(), pluginName, 64);
				LogError("Failed to auto generate config for %s at '%s', make sure the directory has write permission.", pluginName, local);
				if(clearWhenDone)
				{
					delete _ConvarList;
				}
				return false;
			}
		}
		// file already exists, just update the description and defaults
		else
		{
			// open the file for reading
			File file = OpenFile(local, "r");
			// create a stringmap to hold our current convars.
			StringMap convars = new StringMap();

			char line[512];
			int currentLine = 0;
			while(!file.EndOfFile() && file.ReadLine(line, 512))
			{
				++currentLine;
				// check if the line contains a valid statement
				if(line[0] != '/' && line[0] != '\n' && line[0] != 0)
				{
					char buffers[2][512];
					// only convars should be in here. which should contain convar [space] value.
					if(ExplodeString(line, " ", buffers, 2, 512, true) == 2)
					{
						// remove any trailing whitespace. should be none
						TrimString(buffers[0]);
						TrimString(buffers[1]);
						// remove the quotes from the values
						StripQuotes(buffers[0]);
						StripQuotes(buffers[1]);

						// since convars are only ever strings we store it as strings.
						convars.SetString(buffers[0], buffers[1]);
					}
					else
					{
						// someone put something in there that shouldn't be. Yell at the dev for doing stupid stuff.
						LogError("Error exploding convar string: '%s' on line: %i", line, currentLine);
					}
				}
			}
			// close our file
			delete file;
			// yay duplicate code
			// rewrite the cfg with old convars removed and new convars added.
			/* Attempt to recreate it */
			DeleteFile(local);
			file = OpenFile(local, "wt");
			if (file != null)
			{
				fileExists = true;

				char pluginName[64];
				GetPluginFilename(GetMyHandle(), pluginName, 64);

				file.WriteLine("// This file was auto-generated by KiD's Convar Class. Only plugin convars are allowed.");
				file.WriteLine("// ConVars for plugin \"%s\"\n\n", pluginName);

				float x;
				for (int i = 0; i < _ConvarList.Length; ++i)
				{
					convar_t convar;
					_ConvarList.GetArray(i, convar);

					if (convar.cvar.Flags & FCVAR_DONTRECORD != 0)
					{
						continue;
					}

					char descr[512];
					descr = convar.description;

					ReplaceString(descr, 512, "\n", "\n// ");
					file.WriteLine("// %s", descr);

					char name[64];
					convar.cvar.GetName(name, 64);

					file.WriteLine("// -");
					file.WriteLine("// Default: \"%s\"", convar.defValue);
					if (convar.GetMin(x))
					{
						file.WriteLine("// Minimum: \"%02f\"", x);
					}
					if (convar.GetMax(x))
					{
						file.WriteLine("// Maximum: \"%02f\"", x);
					}
					
					// only difference is that now we check for a stored value.
					char storedValue[512];
					if(convars.GetString(name, storedValue, 512))
					{
						file.WriteLine("%s \"%s\"\n", name, storedValue);
					}
					else
					{
						file.WriteLine("%s \"%s\"\n", name, convar.defValue);
					}
				}
				file.WriteLine("");
				delete file;
			}
			else
			{
				char pluginName[64];
				GetPluginFilename(GetMyHandle(), pluginName, 64);
				LogError("Failed to auto generate config for %s, make sure the directory has write permission.", pluginName);
				
				if(clearWhenDone)
				{
					delete _ConvarList;
				}

				delete convars;
				return false;
			}
		
			delete convars;
		}
		if(fileExists)
		{
			ServerCommand("exec \"%s\"", local[4]);
		}
		if(clearWhenDone)
		{
			delete _ConvarList;
		}
		return true;
	}

	public static void AutoExecConfig(const char[] fileName = "", const char[] folder = "sourcemod", const bool clearWhenDone = true)
	{
		if(Convar.CreateConfig(fileName, folder, clearWhenDone))
		{
			AutoExecConfig(false, fileName, folder);
		}
	}

	public void Close()
	{
		delete _ConvarList;
	}
}


================================================
FILE: scripting/lilac/lilac_aimbot.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

static int aimbot_detection[MAXPLAYERS + 1];
static int aimbot_autoshoot[MAXPLAYERS + 1];
static int aimbot_timertick[MAXPLAYERS + 1];

void lilac_aimbot_reset_client(int client)
{
	aimbot_detection[client] = 0;
	aimbot_autoshoot[client] = 0;
	aimbot_timertick[client] = 0;
}

int lilac_aimbot_get_client_detections(int client)
{
	return aimbot_detection[client];
}

public Action event_player_death(Event event, const char[] name, bool dontBroadcast)
{
	int attackerid;
	int victimid;
	int client;
	
	if (!icvar[CVAR_ENABLE])
		return Plugin_Continue;
	
	attackerid = GetEventInt(event, "attacker", -1);
	victimid = GetEventInt(event, "userid", -1);
	client = GetClientOfUserId(victimid);
	
	if (!is_player_valid(client))
		return Plugin_Continue;
	
	/* This prevents running multiple aimbot checks on the same tick.
	 * This can happen with explosives, like some projectiles.
	 * This variable gets set in the "shared event" function. */
	if (aimbot_timertick[client] == GetGameTickCount())
		return Plugin_Continue;
	
	event_death_shared(attackerid,
		GetClientOfUserId(attackerid),
		client, false);
	
	return Plugin_Continue;
}

public Action event_player_death_tf2(Event event, const char[] name, bool dontBroadcast)
{
	char wep[64];
	int userid, client, victim, killtype;

	if (!icvar[CVAR_ENABLE])
		return Plugin_Continue;


	victim = GetClientOfUserId(GetEventInt(event, "userid", -1));

	if (!is_player_valid(victim))
		return Plugin_Continue;
	
	/* Same as above, prevent multiple aimbot checks on the same tick. */
	if (aimbot_timertick[victim] == GetGameTickCount())
		return Plugin_Continue;

	GetEventString(event, "weapon_logclassname", wep, sizeof(wep), "");

	/* Ignore sentries & world in TF2. */
	if (!strncmp(wep, "obj_", 4, false) || !strncmp(wep, "world", 5, false))
		return Plugin_Continue;

	userid = GetEventInt(event, "attacker", -1);
	client = GetClientOfUserId(userid);
	killtype = GetEventInt(event, "customkill", 0);

	/* killtype 3 == flamethrower,
	 * ignore snap detections as pyros sometimes,
	 * sway their aim around. */
	event_death_shared(userid, client, victim, ((killtype == 3) ? true : false));

	return Plugin_Continue;
}

void event_death_shared(int userid, int client, int victim, bool skip_delta)
{
	DataPack pack;
	float killpos[3], deathpos[3];
	int skip_snap = 0;

	if (client == victim
		|| !is_player_valid(client)
		/* || !is_player_valid(victim) Already checked. */
		|| IsFakeClient(client)
		|| !IsPlayerAlive(client)
		|| playerinfo_banned_flags[client][CHEAT_AIMBOT]
		|| GetClientTime(client) < 10.1)
		return;

	if (icvar[CVAR_AIMLOCK_LIGHT])
		lilac_aimlock_light_test(client);

	if (!icvar[CVAR_AIMBOT])
		return;

	/* Prevent multiple aimbot timer checks on the same tick. */
	aimbot_timertick[client] = GetGameTickCount();

	GetClientEyePosition(client, killpos);
	GetClientEyePosition(victim, deathpos);

	/* Killer and victim are too close to each other, skip some detections. */
	if (GetVectorDistance(killpos, deathpos) < 350.0 || skip_delta)
		skip_snap = 1;

	CreateDataTimer(0.5, timer_check_aimbot, pack);
	pack.WriteCell(userid);
	pack.WriteCell(skip_snap);
	pack.WriteCell(playerinfo_index[client]); /* Fallback to this tick if the shot isn't found. */
	pack.WriteFloat(killpos[0]);
	pack.WriteFloat(killpos[1]);
	pack.WriteFloat(killpos[2]);
	pack.WriteFloat(deathpos[0]);
	pack.WriteFloat(deathpos[1]);
	pack.WriteFloat(deathpos[2]);
}

public Action timer_check_aimbot(Handle timer, DataPack pack)
{
	int ind;
	int client;
	int fallback;
	int shotindex = -1;
	int detected = 0;
	float delta = 0.0;
	float tdelta = 0.0;
	float total_delta = 0.0;
	float aimdist, laimdist;
	float ideal[3], ang[3], lang[3];
	float killpos[3], deathpos[3];
	bool skip_snap = false;
	bool skip_autoshoot = false;
	bool skip_repeat = false;

	pack.Reset();
	client = GetClientOfUserId(pack.ReadCell());
	skip_snap = pack.ReadCell();
	fallback = pack.ReadCell();
	killpos[0] = pack.ReadFloat();
	killpos[1] = pack.ReadFloat();
	killpos[2] = pack.ReadFloat();
	deathpos[0] = pack.ReadFloat();
	deathpos[1] = pack.ReadFloat();
	deathpos[2] = pack.ReadFloat();

	/* Killer may have left the game, cancel. */
	if (!is_player_valid(client))
		return Plugin_Continue;

	/* Locate when the shot was fired. */
	ind = playerinfo_index[client];
	/* 0.5 (datapacktimer delay) + 0.5 (snap test) + 0.1 (buffer).
	 * We are looking this far back in case of a projectile aimbot shot,
	 * as the death event happens way later after the shot. */
	for (int i = 0; i < CMD_LENGTH - time_to_ticks(0.5 + 0.5 + 0.1); i++) {
		if (--ind < 0)
			ind += CMD_LENGTH;

		/* The shot needs to have happened at least 0.3 seconds ago. */
		if (GetGameTime() - playerinfo_time_usercmd[client][ind] < 0.3)
			continue;

		if ((playerinfo_actions[client][ind] & ACTION_SHOT)) {
			shotindex = ind;
			break;
		}
	}

	/* Shot not found, use fallback. */
	if (shotindex == -1) {
		shotindex = fallback;

		/* If the latest index is the same as the fallback, then no
		 * more usercmds have been processed since the death event.
		 * These detections are thus unstable and will be ignored
		 * (They require at least one tick after the shot to work). */
		if (playerinfo_index[client] == fallback) {
			skip_autoshoot = true;
			skip_repeat = true;
		}
	}
	else {
		/* Don't detect the same shot twice. */
		playerinfo_actions[client][shotindex] = 0;
	}

	/* Skip repeat detections if players are too close to each other. */
	if (skip_snap)
		skip_repeat = true;

	/* Player taunted within 0.5 seconds
	 * of taking a shot leading to a kill.
	 * Ignore snap detections. */
	if (-0.1 < playerinfo_time_usercmd[client][shotindex] - playerinfo_time_teleported[client] < 0.5 + 0.1)
		skip_snap = true;

	/* Aimsnap and total delta test. */
	if (skip_snap == false) {
		aim_at_point(killpos, deathpos, ideal);

		ind = shotindex;
		/* Check angle history 0.5 seconds prior to a shot. */
		for (int i = 0; i < time_to_ticks(0.5); i++) {
			if (ind < 0)
				ind += CMD_LENGTH;

			/* We're looking back further than 0.5 seconds prior to the shot, abort. */
			if (playerinfo_time_usercmd[client][shotindex] - playerinfo_time_usercmd[client][ind] > 0.5)
				break;

			laimdist = angle_delta(playerinfo_angles[client][ind], ideal);
			get_player_log_angles(client, ind, false, ang);

			/* Skip first iteration as we need angle deltas. */
			if (i) {
				tdelta = angle_delta(lang, ang);

				/* Store largest delta. */
				if (tdelta > delta)
					delta = tdelta;

				total_delta += tdelta;

				if (aimdist < laimdist * 0.2 && tdelta > 10.0)
					detected |= AIMBOT_FLAG_SNAP;

				if (aimdist < laimdist * 0.1 && tdelta > 5.0)
					detected |= AIMBOT_FLAG_SNAP2;
			}

			lang = ang;
			aimdist = laimdist;
			ind--;
		}
	}

	/* Packetloss is too high, skip all detections but total_delta. */
	if (skip_due_to_loss(client)) {
		skip_autoshoot = true;
		skip_repeat = true;
		detected = 0;
	}

	/* Angle-repeat test. */
	if (skip_repeat == false) {
		get_player_log_angles(client, shotindex - 1, false, ang);
		get_player_log_angles(client, shotindex + 1, false, lang);
		tdelta = angle_delta(ang, lang);
		get_player_log_angles(client, shotindex, false, lang);

		if (tdelta < 10.0 && angle_delta(ang, lang) > 0.5
			&& angle_delta(ang, lang) > tdelta * 5.0)
			detected |= AIMBOT_FLAG_REPEAT;
	}

	/* Autoshoot test. */
	if (skip_autoshoot == false && icvar[CVAR_AIMBOT_AUTOSHOOT]) {
		int tmp = 0;
		ind = shotindex + 1;
		for (int i = 0; i < 3; i++) {
			if (ind < 0)
				ind += CMD_LENGTH;
			else if (ind >= CMD_LENGTH)
				ind -= CMD_LENGTH;

			if ((playerinfo_buttons[client][ind] & IN_ATTACK))
				tmp++;

			ind--;
		}

		/* Onetick perfect shot.
		 * Players must get two of them in a row leading to a kill
		 * or something else must have been detected to get this flag. */
		if (tmp == 1) {
			if (detected || ++aimbot_autoshoot[client] > 1)
				detected |= AIMBOT_FLAG_AUTOSHOOT;
		}
		else {
			aimbot_autoshoot[client] = 0;
		}
	}

	if (detected || total_delta > AIMBOT_MAX_TOTAL_DELTA)
		lilac_detected_aimbot(client, delta, total_delta, detected);

	return Plugin_Continue;
}

static void lilac_detected_aimbot(int client, float delta, float td, int flags)
{
	if (playerinfo_banned_flags[client][CHEAT_AIMBOT])
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_AIMBOT) == false)
		return;

	/* Detection expires in 10 minutes. */
	CreateTimer(600.0, timer_decrement_aimbot, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);

	lilac_forward_client_cheat(client, CHEAT_AIMBOT);

	/* Don't log the first detection. */
	if (++aimbot_detection[client] < 2)
		return;

	if (icvar[CVAR_CHEAT_WARN])
		lilac_warn_admins(client, CHEAT_AIMBOT, aimbot_detection[client]);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s is suspected of using an aimbot (Detection: %d | Delta: %.0f | TotalDelta: %.0f | Detected:%s%s%s%s%s).",
			line_buffer, aimbot_detection[client], delta, td,
			((flags & AIMBOT_FLAG_SNAP)      ? " Aim-Snap"     : ""),
			((flags & AIMBOT_FLAG_SNAP2)     ? " Aim-Snap2"    : ""),
			((flags & AIMBOT_FLAG_AUTOSHOOT) ? " Autoshoot"    : ""),
			((flags & AIMBOT_FLAG_REPEAT)    ? " Angle-Repeat" : ""),
			((td > AIMBOT_MAX_TOTAL_DELTA)   ? " Total-Delta"  : ""));

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA] == 2)
			lilac_log_extra(client);
	}
	database_log(client, "aimbot", aimbot_detection[client], float(flags), td);

	if (aimbot_detection[client] >= icvar[CVAR_AIMBOT]
		&& icvar[CVAR_AIMBOT] >= AIMBOT_BAN_MIN) {

		if (icvar[CVAR_LOG]) {
			lilac_log_setup_client(client);
			Format(line_buffer, sizeof(line_buffer),
				"%s was banned for Aimbot.", line_buffer);

			lilac_log(true);

			if (icvar[CVAR_LOG_EXTRA])
				lilac_log_extra(client);
		}
		database_log(client, "aimbot", DATABASE_BAN);

		playerinfo_banned_flags[client][CHEAT_AIMBOT] = true;
		lilac_ban_client(client, CHEAT_AIMBOT);
	}
}

public Action timer_decrement_aimbot(Handle timer, int userid)
{
	int client = GetClientOfUserId(userid);

	if (!is_player_valid(client))
		return Plugin_Continue;

	if (aimbot_detection[client] > 0)
		aimbot_detection[client]--;

	return Plugin_Continue;
}


================================================
FILE: scripting/lilac/lilac_aimlock.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

static bool aimlock_skip_player(int client)
{
	if (!is_player_valid(client)
		|| IsFakeClient(client)
		|| !IsPlayerAlive(client)
		|| GetClientTeam(client) < 2 /* Not on a valid team. */
		|| GetGameTime() - playerinfo_time_teleported[client] < 2.0 /* Player recently teleported. */
		|| skip_due_to_loss(client)
		|| playerinfo_banned_flags[client][CHEAT_AIMLOCK]) /* Already banned/logged. */
		return true;

	/* Lightweight mode is enabled, don't process players who aren't in que. */
	if (icvar[CVAR_AIMLOCK_LIGHT] == 1 && lilac_is_player_in_aimlock_que(client) == false)
		return true;

	return false;
}

static bool aimlock_skip_target(int client, int target)
{
	return (client == target
		|| !is_player_valid(target)
		|| GetClientTeam(client) == GetClientTeam(target)
		|| !IsPlayerAlive(target)
		|| GetClientTeam(target) < 2 /* Target isn't in a valid team. */
		|| GetGameTime() - playerinfo_time_teleported[target] < 2.0); /* Teleported. */
}

public Action timer_check_aimlock(Handle timer)
{
	float pos[3], pos2[3];
	int players_processed = 0;
	bool detected_aimlock[MAXPLAYERS + 1];

	if (!icvar[CVAR_ENABLE] || !icvar[CVAR_AIMLOCK])
		return Plugin_Continue;

	for (int client = 1; client <= MaxClients; client++) {
		detected_aimlock[client] = false;

		/* Don't process more than 5 players.
		 * Note: Don't use a "break" statement here!
		 * We need to set detected_aimlock[...] to false
		 * on every single player!
		 * This will then also serve as a "is_player_valid()"
		 * for players who need to be detected for Aimlock. */
		if (icvar[CVAR_AIMLOCK_LIGHT] == 1 && players_processed >= 5)
			continue;

		if (aimlock_skip_player(client))
			continue;

		GetClientEyePosition(client, pos);

		players_processed++;

		bool process = true;
		for (int target = 1; process && target <= MaxClients; target++) {
			if (aimlock_skip_target(client, target))
				continue;

			GetClientEyePosition(target, pos2);

			/* Too close to an enemy, don't report aimlock
			 * detections and stop processing this player. */
			if (GetVectorDistance(pos, pos2) < 300.0) {
				detected_aimlock[client] = false;
				process = false;
				continue;
			}

			/* Player has already been detected of using aimlock,
			 * don't check for aimlock again, only check
			 * if the player is too close to other enemies. */
			if (detected_aimlock[client])
				continue;

			if (is_aimlocking(client, pos, pos2))
				detected_aimlock[client] = true;
		}
	}

	for (int i = 1; i <= MaxClients; i++) {
		if (detected_aimlock[i])
			lilac_detected_aimlock(i);
	}

	return Plugin_Continue;
}

static bool is_aimlocking(int client, float pos[3], float pos2[3])
{
	float ideal[3], lang[3], ang[3];
	float laimdist, aimdist;
	int lock = 0;
	int ind;

	aim_at_point(pos, pos2, ideal);

	ind = playerinfo_index[client];
	for (int i = 0; i < time_to_ticks(0.5 + 0.1); i++) {
		if (ind < 0)
			ind += CMD_LENGTH;

		/* Only process aimlock time. */
		if (GetGameTime() - playerinfo_time_usercmd[client][ind] < 0.5 + 0.1) {
			get_player_log_angles(client, ind, false, ang);
			laimdist = angle_delta(ang, ideal);

			if (i) {
				if (aimdist < 5.0)
					lock++;
				else
					lock = 0;

				if (aimdist < laimdist * 0.1
					&& angle_delta(ang, lang) > 20.0
					&& lock > time_to_ticks(0.1))
					return true;
			}

			lang = ang;
			aimdist = laimdist;
		}

		ind--;
	}

	return false;
}

static void lilac_detected_aimlock(int client)
{
	if (playerinfo_banned_flags[client][CHEAT_AIMLOCK])
		return;

	/* Suspicions reset after 3 minutes.
	 * This means you need to get two aimlocks within
	 * three minutes of each other to get a single detection. */
	if (GetGameTime() - playerinfo_time_aimlock[client] < 180.0)
		playerinfo_aimlock_sus[client]++;
	else
		playerinfo_aimlock_sus[client] = 1;

	playerinfo_time_aimlock[client] = GetGameTime();

	if (playerinfo_aimlock_sus[client] < 2)
		return;

	playerinfo_aimlock_sus[client] = 0;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_AIMLOCK) == false)
		return;

	/* Detection expires in 10 minutes. */
	CreateTimer(600.0, timer_decrement_aimlock, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE);

	lilac_forward_client_cheat(client, CHEAT_AIMLOCK);

	/* Don't log the first detection. */
	if (++playerinfo_aimlock[client] < 2)
		return;

	if (icvar[CVAR_CHEAT_WARN])
		lilac_warn_admins(client, CHEAT_AIMLOCK, playerinfo_aimlock[client]);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s is suspected of using an aimlock (Detection: %d).",
			line_buffer, playerinfo_aimlock[client]);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA] == 2)
			lilac_log_extra(client);
	}
	database_log(client, "aimlock", playerinfo_aimlock[client]);

	if (playerinfo_aimlock[client] >= icvar[CVAR_AIMLOCK]
		&& icvar[CVAR_AIMLOCK] >= AIMLOCK_BAN_MIN) {
		playerinfo_banned_flags[client][CHEAT_AIMLOCK] = true;

		if (icvar[CVAR_LOG]) {
			lilac_log_setup_client(client);
			Format(line_buffer, sizeof(line_buffer),
				"%s was banned for Aimlock.", line_buffer);

			lilac_log(true);

			if (icvar[CVAR_LOG_EXTRA])
				lilac_log_extra(client);
		}
		database_log(client, "aimlock", DATABASE_BAN);

		lilac_ban_client(client, CHEAT_AIMLOCK);
	}
}

void lilac_aimlock_light_test(int client)
{
	int ind;
	float lastang[3], ang[3];

	/* Player recently teleported, spawned or taunted. Ignore. */
	if (GetGameTime() - playerinfo_time_teleported[client] < 3.0)
		return;

	ind = playerinfo_index[client];
	for (int i = 0; i < time_to_ticks(0.5); i++) {
		if (ind < 0)
			ind += CMD_LENGTH;

		get_player_log_angles(client, ind, false, ang);

		if (i) {
			/* This player has a somewhat big delta,
			 * test this player for aimlock for 200 seconds.
			 * Even if we end up flagging more than 5 players
			 * for this, that's fine as only 5 players
			 * can be processed in the aimlock check timer. */
			if (angle_delta(lastang, ang) > 20.0) {
				playerinfo_time_process_aimlock[client] = GetGameTime() + 200.0;
				return;
			}
		}

		lastang = ang;
		ind--;
	}
}

static bool lilac_is_player_in_aimlock_que(int client)
{
	/* Test for aimlock on players who: */
	return (GetGameTime() < playerinfo_time_process_aimlock[client] /* Are in the que. */
		|| playerinfo_aimlock[client] /* Already has a detection. */
		|| lilac_aimbot_get_client_detections(client) > 1 /* Already have been detected for aimbot twice. */
		|| GetClientTime(client) < 240.0 /* Client just joined the game. */
		|| (GetGameTime() - playerinfo_time_aimlock[client] < 180.0
			&& playerinfo_time_aimlock[client] > 1.0)); /* Had one aimlock the past three minutes. */
}

public Action timer_decrement_aimlock(Handle timer, int userid)
{
	int client = GetClientOfUserId(userid);

	if (!is_player_valid(client))
		return Plugin_Continue;

	if (playerinfo_aimlock[client] > 0)
		playerinfo_aimlock[client]--;

	return Plugin_Continue;
}


================================================
FILE: scripting/lilac/lilac_angles.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

void lilac_angles_check(int client, float angles[3])
{
	/* Angles in L4D1&2 aren't always normalized properly. */
	if (ggame == GAME_L4D2 || ggame == GAME_L4D)
		return;

	if (!IsPlayerAlive(client)
		|| playerinfo_time_teleported[client] + 5.0 > GetGameTime())
		return;

	/* In TF2, if players use the bumpercarts outside of
	 * official halloween map areas while standing on
	 * weird inclines, you can trigger a false positive.
	 * Yes... It's weird... Yes, this is rare and only happens
	 * on community servers where they provide carts outside
	 * of official halloween map areas...
	 * Anyway, thanks WOLFA22 for reporting this! */
#if !defined TF2C
	if (ggame == GAME_TF2) {
		if (TF2_IsPlayerInCondition(client, TFCond_HalloweenKart)) {
			playerinfo_time_bumpercart[client] = GetGameTime();
			return;
		}
		else if (GetGameTime() - playerinfo_time_bumpercart[client] < 5.0) {
			return;
		}
	}
#endif

	if ((FloatAbs(angles[0]) > max_angles[0] && max_angles[0])
		|| (FloatAbs(angles[2]) > max_angles[2] && max_angles[2]))
		lilac_detected_angles(client, angles);
}

void lilac_angles_patch(float angles[3])
{
	/* Patch Pitch. */
	if (max_angles[0] != 0.0) {
		if (angles[0] > max_angles[0])
			angles[0] = max_angles[0];
		else if (angles[0] < (max_angles[0] * -1.0))
			angles[0] = (max_angles[0] * -1.0);
	}

	/* Patch roll. */
	angles[2] = 0.0;
}

static void lilac_detected_angles(int client, float ang[3])
{
	if (playerinfo_banned_flags[client][CHEAT_ANGLES])
		return;

	/* Spam prevention. */
	if (playerinfo_time_forward[client][CHEAT_ANGLES] > GetGameTime())
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_ANGLES) == false) {
		/* Don't spam this forward again for the next 20 seconds. */
		playerinfo_time_forward[client][CHEAT_ANGLES] = GetGameTime() + 20.0;
		return;
	}

	playerinfo_banned_flags[client][CHEAT_ANGLES] = true;

	lilac_forward_client_cheat(client, CHEAT_ANGLES);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s was detected and banned for Angle-Cheats (Pitch: %.2f, Yaw: %.2f, Roll: %.2f).",
			line_buffer, ang[0], ang[1], ang[2]);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}

	/* no need to add more data, these 3 angles are already included. */
	database_log(client, "angles", DATABASE_BAN);

	lilac_ban_client(client, CHEAT_ANGLES);
}


================================================
FILE: scripting/lilac/lilac_anti_duck_delay.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/* Delet dis! */
#if defined TF2C
	#endinput
#endif

void lilac_anti_duck_delay_check(int client, const int buttons)
{
	if (!(buttons & IN_BULLRUSH))
		return;

	if (playerinfo_banned_flags[client][CHEAT_ANTI_DUCK_DELAY])
		return;

	/* Spam prevention. */
	if (playerinfo_time_forward[client][CHEAT_ANTI_DUCK_DELAY] > GetGameTime())
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_ANTI_DUCK_DELAY) == false) {
		/* Don't spam this forward again for the next 10 seconds. */
		playerinfo_time_forward[client][CHEAT_ANTI_DUCK_DELAY] = GetGameTime() + 10.0;
		return;
	}

	playerinfo_banned_flags[client][CHEAT_ANTI_DUCK_DELAY] = true;

	lilac_forward_client_cheat(client, CHEAT_ANTI_DUCK_DELAY);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer), "%s was detected and banned for Anti-Duck-Delay.", line_buffer);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}
	database_log(client, "anti_duck_delay", DATABASE_BAN);

	lilac_ban_client(client, CHEAT_ANTI_DUCK_DELAY);
}


================================================
FILE: scripting/lilac/lilac_backtrack.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

static int prev_tickcount[MAXPLAYERS + 1];
static int diff_tickcount[MAXPLAYERS + 1];
static float time_timeout[MAXPLAYERS + 1];

void lilac_backtrack_reset_client(int client)
{
	prev_tickcount[client] = 0;
	diff_tickcount[client] = 0;
	time_timeout[client] = 0.0;
}

void lilac_backtrack_store_tickcount(int client, int tickcount)
{
	static int tmp[MAXPLAYERS + 1];

	prev_tickcount[client] = tmp[client];
	tmp[client] = tickcount;
}

int lilac_backtrack_patch(int client, int tickcount)
{
	/* Skip players who recently teleported. */
	if (playerinfo_time_teleported[client] + 2.0 > GetGameTime())
		return tickcount;

	if (lilac_valid_tickcount(client, tickcount) == false
		&& lilac_is_player_in_backtrack_timeout(client) == false)
		lilac_set_client_in_backtrack_timeout(client);

	if (lilac_is_player_in_backtrack_timeout(client)
		&& icvar[CVAR_BACKTRACK_PATCH])
		return lilac_lock_tickcount(client);

	return tickcount;
}

static int lilac_lock_tickcount(int client)
{
	int ping, tick;

	ping = RoundToNearest(GetClientAvgLatency(client, NetFlow_Outgoing) / GetTickInterval());
	tick = diff_tickcount[client] + (GetGameTickCount() - ping);

	/* Never return higher than server tickcount.
	 * Other than that, lock the tickcount to the player's
	 * previous value for the durration of the patch.
	 * This patch method shouldn't affect legit laggy players as much. */
	return ((tick > GetGameTickCount()) ? GetGameTickCount() : tick);
}

static bool lilac_valid_tickcount(int client, int tickcount)
{
	return (intabs((prev_tickcount[client] + 1) - tickcount) <= icvar[CVAR_BACKTRACK_TOLERANCE]);
}

static void lilac_set_client_in_backtrack_timeout(int client)
{
	/* Set the player in backtrack timeout for 1.1 seconds. */
	time_timeout[client] = GetGameTime() + 1.1;

	/* Lock value. */
	diff_tickcount[client] = (prev_tickcount[client] - (GetGameTickCount() - RoundToNearest(GetClientAvgLatency(client, NetFlow_Outgoing) / GetTickInterval()))) + 1;

	/* Clamp the value due to floating point errors and network variability. */
	if (diff_tickcount[client] > time_to_ticks(0.2) - 3)
		diff_tickcount[client] = time_to_ticks(0.2) - 3;
	else if (diff_tickcount[client] < ((time_to_ticks(0.2) * -1) + 3))
		diff_tickcount[client] = (time_to_ticks(0.2) * -1) + 3;
}

static bool lilac_is_player_in_backtrack_timeout(int client)
{
	return (GetGameTime() < time_timeout[client]);
}


================================================
FILE: scripting/lilac/lilac_bhop.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

static int jump_ticks[MAXPLAYERS + 1];
static int perfect_bhops[MAXPLAYERS + 1];
static int next_bhop[MAXPLAYERS + 1];
static int detections[MAXPLAYERS + 1];


static void bhop_reset(int client)
{
	/* -1 because the initial jump doesn't count. */
	jump_ticks[client] = -1;
	perfect_bhops[client] = -1;
	next_bhop[client] = GetGameTickCount();
}

void lilac_bhop_reset_client(int client)
{
	bhop_reset(client);
	detections[client] = 0;
}

void lilac_bhop_check(int client, const int buttons, int last_buttons)
{
	/* Player already banned / logged enough, no need to test anything. */
	if (playerinfo_banned_flags[client][CHEAT_BHOP])
		return;

	if ((buttons & IN_JUMP))
		jump_ticks[client]++;

	int flags = GetEntityFlags(client);
	if ((buttons & IN_JUMP) && !(last_buttons & IN_JUMP)) {
		if ((flags & FL_ONGROUND)) {
			if (GetGameTickCount() > next_bhop[client]) {
				next_bhop[client] = GetGameTickCount() + bhop_settings[BHOP_INDEX_AIR];
				perfect_bhops[client]++;
				check_bhop_max(client);
			}
			else {
				bhop_reset(client);
			}
		}
	}
	else if ((flags & FL_ONGROUND)) {
		check_bhop_min(client);
		bhop_reset(client);
	}
}

static void check_bhop_max(int client)
{
	/* Invalid max, disable max bhop bans. */
	if (bhop_settings[BHOP_INDEX_MAX] < bhop_settings_min[BHOP_INDEX_MAX])
		return;

	if (perfect_bhops[client] < bhop_settings[BHOP_INDEX_MAX])
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_BHOP) == false)
		return;

	/* Client just hit the max threshhold, insta ban. */
	lilac_detected_bhop(client, true, true);
	lilac_ban_bhop(client);
}

static void check_bhop_min(int client)
{
	/* Invalid min-Bhop settings. */
	if (bhop_settings[BHOP_INDEX_MIN] < bhop_settings_min[BHOP_INDEX_MIN])
		return;

	if (perfect_bhops[client] < bhop_settings[BHOP_INDEX_MIN])
		return;

	/* Jump ticks buffer is set and jump ticks is higher than max, ignore. */
	if (bhop_settings[BHOP_INDEX_JUMP] > -1
		&& jump_ticks[client] > bhop_settings[BHOP_INDEX_JUMP]
		+ bhop_settings[BHOP_INDEX_MIN])
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_BHOP) == false)
		return;

	lilac_detected_bhop(client, false, false);
}

static void lilac_detected_bhop(int client, bool force_log, bool banning)
{
	lilac_forward_client_cheat(client, CHEAT_BHOP);

	/* Detection expires in 10 minutes. */
	CreateTimer(600.0, timer_decrement_bhop, GetClientUserId(client));

	/* Don't log the first detection. */
	if (++detections[client] < 2 && force_log == false)
		return;

	if (icvar[CVAR_CHEAT_WARN]
		&& !banning
		&& detections[client] < bhop_settings[BHOP_INDEX_TOTAL])
		lilac_warn_admins(client, CHEAT_BHOP, detections[client]);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s is suspected of using Bhop (Detection: %d | Bhops: %d | JumpTicks: %d).",
			line_buffer, detections[client], perfect_bhops[client],
			jump_ticks[client]);
		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA] == 2)
			lilac_log_extra(client);
	}
	database_log(client, "bhop", detections[client], float(perfect_bhops[client]), float(jump_ticks[client]));

	if (detections[client] >= bhop_settings[BHOP_INDEX_TOTAL])
		lilac_ban_bhop(client);
}

static void lilac_ban_bhop(int client)
{
	/* Already been banned, ignore. */
	if (playerinfo_banned_flags[client][CHEAT_BHOP])
		return;

	playerinfo_banned_flags[client][CHEAT_BHOP] = true;

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s was detected and banned for Bhop.",
			line_buffer);
		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}
	database_log(client, "bhop", DATABASE_BAN);

	lilac_ban_client(client, CHEAT_BHOP);
}

public Action timer_decrement_bhop(Handle timer, int userid)
{
	int client;

	client = GetClientOfUserId(userid);

	if (!is_player_valid(client))
		return Plugin_Continue;

	if (detections[client] > 0)
		detections[client]--;

	return Plugin_Continue;
}


================================================
FILE: scripting/lilac/lilac_config.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

void lilac_config_setup()
{
	ConVar tcvar;

	hcvar[CVAR_ENABLE] = new Convar("lilac_enable", "1",
		"Enable Little Anti-Cheat.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_WELCOME] = new Convar("lilac_welcome", "0",
		"Welcome connecting players saying that the server is protected.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_SB] = new Convar("lilac_sourcebans", "1",
		"Ban players via sourcebans++ (If it isn't installed, it will default to basebans).",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_MA] = new Convar("lilac_materialadmin", "1",
		"Ban players via Material-Admin (Fork of Sourcebans++. If it isn't installed, will default to sourcebans++ or basebans).",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_SOURCEIRC] = new Convar("lilac_sourceirc", "1",
		"Enable reflecting log messages to SourceIRC channels flagged with 'lilac', if SourceIRC is available.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_LOG] = new Convar("lilac_log", "1",
		"Enable cheat logging.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_LOG_EXTRA] = new Convar("lilac_log_extra", "1",
		"0 = Disabled.\n1 = Log extra information on player banned.\n2 = Log extra information on everything.",
		FCVAR_PROTECTED, true, 0.0, true, 2.0);
	hcvar[CVAR_LOG_MISC] = new Convar("lilac_log_misc", "0",
		"Log when players are kicked for misc features, like interp exploits, too high ping and on convar response failure.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_LOG_DATE] = new Convar("lilac_log_date", "{year}/{month}/{day} {hour}:{minute}:{second}",
		"Which date & time format to use when logging. Type: \"lilac_date_list\" for more info.",
		FCVAR_PROTECTED, false, 0.0, false, 0.0);
	hcvar[CVAR_BAN] = new Convar("lilac_ban", "1",
		"Enable banning of cheaters, set to 0 if you want to test Lilac before fully trusting it with bans.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_BAN_LENGTH] = new Convar("lilac_ban_length", "0",
		"How long bans should last in minutes (0 = forever).",
		FCVAR_PROTECTED, true, 0.0, false, 0.0);
	hcvar[CVAR_BAN_LANGUAGE] = new Convar("lilac_ban_language", "1",
		"Ban reason language.\n0 = Use server's language.\n1 = Use the language of the cheater.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_CHEAT_WARN] = new Convar("lilac_cheat_warning", "1",
		"Alert admins in chat about potential cheaters.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_ANGLES] = new Convar("lilac_angles", "1",
		"Detect Angle-Cheats (Basic Anti-Aim, Legit Anti-Backstab and Duckspeed).\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.",
		FCVAR_PROTECTED, true, -1.0, true, 1.0);
	hcvar[CVAR_PATCH_ANGLES] = new Convar("lilac_angles_patch", "1",
		"Patch Angle-Cheats (Basic Anti-Aim, Legit Anti-Backstab and Duckspeed).",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_CHAT] = new Convar("lilac_chatclear", "1",
		"Detect Chat-Clear.\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.",
		FCVAR_PROTECTED, true, -1.0, true, 1.0);
	hcvar[CVAR_CONVAR] = new Convar("lilac_convar", "1",
		"Detect basic invalid ConVars.\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.",
		FCVAR_PROTECTED, true, -1.0, true, 1.0);
	hcvar[CVAR_NOLERP] = new Convar("lilac_nolerp", "1",
		"Detect NoLerp.\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.",
		FCVAR_PROTECTED, true, -1.0, true, 1.0);
	hcvar[CVAR_BHOP] = new Convar("lilac_bhop", "5",
		"Bhop detection mode (Negative values = log-only).\n0 = Disabled.\n1&2 = Reserved (Invalid).\n3 = Custom (unlocks lilac_bhop_set command).\n4 = Low.\n5 = Medium.\n6 = High.",
		FCVAR_PROTECTED, true, -6.0, true, 6.0);
	hcvar[CVAR_AIMBOT] = new Convar("lilac_aimbot", "5",
		"Detect basic Aimbots.\n0 = Disabled.\n1 = Log only.\n5 or more = ban on n'th detection (Minimum possible is 5)",
		FCVAR_PROTECTED, true, 0.0, false, 0.0);
	hcvar[CVAR_AIMBOT_AUTOSHOOT] = new Convar("lilac_aimbot_autoshoot", "1",
		"Detect Autoshoot.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_AIMLOCK] = new Convar("lilac_aimlock", "10",
		"Detect Aimlock.\n0 = Disabled.\n1 = Log only.\n5 or more = ban on n'th detection (Minimum possible is 5).",
		FCVAR_PROTECTED, true, 0.0, false, 0.0);
	hcvar[CVAR_AIMLOCK_LIGHT] = new Convar("lilac_aimlock_light", "1",
		"Only process at most 5 suspicious players for aimlock.\nDO NOT DISABLE THIS UNLESS YOUR SERVER CAN HANDLE IT!",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_ANTI_DUCK_DELAY] = new Convar("lilac_anti_duck_delay", "1",
		"CS:GO Only, detect Anti-Duck-Delay/FastDuck.\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.",
		FCVAR_PROTECTED, true, -1.0, true, 1.0);
	hcvar[CVAR_NOISEMAKER_SPAM] = new Convar("lilac_noisemaker", "1",
		"TF2 Only, detect infinite noisemaker spam. STILL IN BETA, DOES NOT BAN, ONLY LOGS! MAY HAVE SOME ISSUES!\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.",
		FCVAR_PROTECTED, true, -1.0, true, 1.0);
	hcvar[CVAR_BACKTRACK_PATCH] = new Convar("lilac_backtrack_patch", "0",
		"Patch Backtrack.\n0 = Disabled (Recommended setting for SMAC compatibility).\n1 = Enabled.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_BACKTRACK_TOLERANCE] = new Convar("lilac_backtrack_tolerance", "0",
		"How tolerant the backtrack patch will be of tickcount changes.\n0 = No tolerance (recommended).\n1+ = n ticks tolerant.",
		FCVAR_PROTECTED, true, 0.0, true, 3.0);
	hcvar[CVAR_MAX_PING] = new Convar("lilac_max_ping", "0",
		"Ban players with too high of a ping for 3 minutes.\nThis is meant to deal with fakelatency, the ban length is just to prevent instant reconnects.\n0 = no ping limit, minimum possible is 100.",
		FCVAR_PROTECTED, true, 0.0, true, 1000.0);
	hcvar[CVAR_MAX_PING_SPEC] = new Convar("lilac_max_ping_spec", "0",
		"Move players with a high ping to spectator and warn them after this many seconds (Minimum possible is 30).",
		FCVAR_PROTECTED, true, 0.0, true, 90.0);
	hcvar[CVAR_MAX_LERP] = new Convar("lilac_max_lerp", "105",
		"Kicks players attempting to exploit interpolation, any interp higher than this value = kick.\nMinimum value possible = 105 (Default interp in games = 100).\n0 or less than 105 = Disabled.",
		FCVAR_PROTECTED, true, 0.0, true, 510.0); /* 500 is max possible. */
	hcvar[CVAR_MACRO] = new Convar("lilac_macro", "0",
		"Detect macros.\n-1 = Log only.\n0 = Disabled.\n1 = Enabled.\n2 = Enabled, but no logging.",
		FCVAR_PROTECTED, true, -1.0, true, 2.0);
	hcvar[CVAR_MACRO_WARNING] = new Convar("lilac_macro_warning", "1",
		"Warning mode for Macro detection:\n0 = No warning.\n1 = Warn player using macro.\n2 = Warn admins on server.\n3 = Warn everyone.",
		FCVAR_PROTECTED, true, 0.0, true, 3.0);
	hcvar[CVAR_MACRO_DEAL_METHOD] = new Convar("lilac_macro_method", "0",
		"What to do with players detected of using macros:\n0 = Kick.\n1 = Ban.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_MACRO_MODE] = new Convar("lilac_macro_mode", "0",
		"What types of macros to detect:\n0 = All.\n1 = Auto-Jump.\n2 = Auto-Shoot.",
		FCVAR_PROTECTED, true, 0.0, true, 3.0);
	hcvar[CVAR_FILTER_NAME] = new Convar("lilac_filter_name", "2",
		"Filter invalid names (kicks players with invalid names).\n-1 = Log-Only.\n0 = Disabled.\n1 = Enabled, kick only.\n2 = Ban cheaters with newlines in names.",
		FCVAR_PROTECTED, true, -1.0, true, 2.0);
	hcvar[CVAR_FILTER_CHAT] = new Convar("lilac_filter_chat", "1",
		"Filter invalid characters in chat (block chat messages).",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_LOSS_FIX] = new Convar("lilac_loss_fix", "1",
		"Ignore some cheat detections for players who have too much packet loss (bad connection to the server).",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_AUTO_UPDATE] = new Convar("lilac_auto_update", "0",
		"Automatically update Little Anti-Cheat.",
		FCVAR_PROTECTED, true, 0.0, true, 1.0);
	hcvar[CVAR_DATABASE] = new Convar("lilac_database", "",
		"Database to log detections to.\nempty = don't log to database\ndatabase name = log to this database (MySQL & SQLite supported)",
		FCVAR_PROTECTED);

	for (int i = 0; i < CVAR_MAX; i++) {
		if (i != CVAR_LOG_DATE)
			icvar[i] = hcvar[i].IntValue;

		hcvar[i].AddChangeHook(cvar_change);
	}

	if ((tcvar = FindConVar("sv_maxupdaterate")) != null) {
		tcvar.AddChangeHook(cvar_change);
		lilac_lerp_maxupdaterate_changed(tcvar.IntValue);
	}
	else {
		/* Assume the value is 0 if we can't fetch it. */
		lilac_lerp_maxupdaterate_changed(0);
	}

	/* If the server allows clients to set the interp ratio to
	 *     whatever they want, then false positives become possible.
	 * Thanks RoseTheFox / Bud for reporting this :) */
	if ((tcvar = FindConVar("sv_client_min_interp_ratio")) != null) {
		tcvar.AddChangeHook(cvar_change);
		lilac_lerp_ratio_changed(tcvar.IntValue);

		if ((tcvar = FindConVar("sv_client_max_interp_ratio")) != null) {
			tcvar.AddChangeHook(cvar_change);
			lilac_lerp_ratio_changed(tcvar.IntValue);
		}
		else {
			lilac_lerp_ratio_changed(0);
		}
	}
	else {
		lilac_lerp_ratio_changed(0);
	}

	if ((tcvar = FindConVar("sv_cheats")) != null) {
		tcvar.AddChangeHook(cvar_change);
		sv_cheats = tcvar.IntValue;
	}
	else {
		sv_cheats = 1;
	}

	RegServerCmd("lilac_date_list", lilac_date_list, "Lists date formatting options", 0);
	RegServerCmd("lilac_set_ban_length", lilac_set_ban_length, "Sets custom ban lengths for specific cheats.", 0);
	RegServerCmd("lilac_ban_status", lilac_ban_status, "Prints banning status to server console.", 0);
	RegServerCmd("lilac_bhop_set", lilac_bhop_set, "Sets Custom Bhop settings", 0);

	/* Legacy check, execute old config location.
	 * Uses github.com/kidfearless/Auto-Exec-Config-Class */
	if (FileExists("cfg/lilac_config.cfg", false, NULL_STRING))
		Convar.CreateConfig("lilac_config", "");
	else
		Convar.CreateConfig("lilac_config", "sourcemod");
}

public void OnConfigsExecuted()
{
	static bool run_status_ban = true;

	if (run_status_ban)
		lilac_ban_status(0);

	/* We need to call this here, in case of a plugin reload. */
	lilac_bhop_set_preset();

	run_status_ban = false;

	Database_OnConfigExecuted();
}

public Action lilac_bhop_set(int args)
{
	char buffer[16];
	float fl = 0.0;
	int value = 0;
	int index = 0;

	if (intabs(icvar[CVAR_BHOP]) != BHOP_MODE_CUSTOM) {
		PrintToServer("Error: Must be in custom mode, set 'lilac_bhop' to %d.",
			BHOP_MODE_CUSTOM);
		return Plugin_Handled;
	}
	else if (args < 2) {
		PrintToServer("Error: too few arguments.\nUsage: lilac_bhop_set <type> <value>");
		PrintToServer("Examples:");
		PrintToServer("\tlilac_bhop_set min 7");
		PrintToServer("\tlilac_bhop_set ticks -1");
		PrintToServer("\tlilac_bhop_set max 15");
		PrintToServer("\tlilac_bhop_set air 0.3");
		PrintToServer("\tlilac_bhop_set total 4");
		PrintToServer("\nType list:");
		PrintToServer("\tmin   - Minimum consecutive perfect bhops to trigger a detection.");
		PrintToServer("\tticks - Jump tick buffer before min is ignored.");
		PrintToServer("\tmax   - Maximum consecutive perfect bhops to trigger an instant ban.");
		PrintToServer("\tair   - Minimum air-time between bhops in seconds.");
		PrintToServer("\ttotal - How many detections before banning.");
		PrintToServer("\n");
		print_current_bhop_settings();

		return Plugin_Handled;
	}

	GetCmdArg(2, buffer, sizeof(buffer));
	fl = StringToFloat(buffer);
	GetCmdArg(1, buffer, sizeof(buffer));

	/* Working with strings is always the least fun part ): */
	if (StrEqual(buffer, "min", false)
		|| StrEqual(buffer, "minimal", false)
		|| StrEqual(buffer, "minimum", false)) {
		index = BHOP_INDEX_MIN;
	}
	else if (StrEqual(buffer, "jump", false)
		|| StrEqual(buffer, "jumps", false)
		|| StrEqual(buffer, "tick", false)
		|| StrEqual(buffer, "ticks", false)
		|| StrEqual(buffer, "buf", false)
		|| StrEqual(buffer, "buff", false)
		|| StrEqual(buffer, "buffer", false)
		|| StrEqual(buffer, "jump_tick", false)
		|| StrEqual(buffer, "jump_ticks", false)
		|| StrEqual(buffer, "jumptick", false)
		|| StrEqual(buffer, "jumpticks", false)) {
		index = BHOP_INDEX_JUMP;
	}
	else if (StrEqual(buffer, "max", false)
		|| StrEqual(buffer, "maximal", false)
		|| StrEqual(buffer, "maximum", false)) {
		index = BHOP_INDEX_MAX;
	}
	else if (StrEqual(buffer, "air", false)
		|| StrEqual(buffer, "air-time", false)
		|| StrEqual(buffer, "airtime", false)) {
		index = BHOP_INDEX_AIR;
	}
	else if (StrEqual(buffer, "tot", false)
		|| StrEqual(buffer, "total", false)
		|| StrEqual(buffer, "all", false)
		|| StrEqual(buffer, "detection", false)
		|| StrEqual(buffer, "detections", false)) {
		index = BHOP_INDEX_TOTAL;
	}
	else {
		PrintToServer("Error: Unknown type \"%s\"", buffer);
		return Plugin_Handled;
	}

	if (index == BHOP_INDEX_AIR) {
		if (fl > 1.0)
			fl = 1.0;
		value = RoundToCeil(tick_rate * fl);
	}
	else {
		value = RoundToNearest(fl);
	}

	if (value < bhop_settings_min[index]) {
		/* Total setting CANNOT be set to be
		 * lower than the min, no matter what! */
		if (index == BHOP_INDEX_TOTAL) {
			value = bhop_settings_min[index];
		}
		else if (value != 0) {
			PrintToServer("Warning: Minimum value is %d, use '0' to disable feature.",
				bhop_settings_min[index]);
			value = bhop_settings_min[index];
		}
	}

	if (index == BHOP_INDEX_AIR)
		PrintToServer("[Lilac] Changed Bhop setting \"%s\" to %d ticks.",
			buffer, value);
	else
		PrintToServer("[Lilac] Changed Bhop setting \"%s\" to %d.",
			buffer, value);

	bhop_settings[index] = value;
	print_current_bhop_settings();

	/* Both being 0 means we won't detect anything, lol.
	 * So let's warn the admin :) */
	if (!bhop_settings[BHOP_INDEX_MIN] && !bhop_settings[BHOP_INDEX_MAX])
		PrintToServer("Warning: Min and Max are set to 0, bhop detection is now disabled!");

	return Plugin_Handled;
}

static void print_current_bhop_settings()
{
	/* Yeah, a little messy... */
	PrintToServer("Current Bhop values:");
	PrintToServer("    Type  : Min possible : Current");
	PrintToServer("    Min   : %2d           : %d", bhop_settings_min[BHOP_INDEX_MIN], bhop_settings[BHOP_INDEX_MIN]);
	if (bhop_settings[BHOP_INDEX_JUMP] == -1)
		PrintToServer("    Ticks : %2d           : %d", bhop_settings_min[BHOP_INDEX_JUMP], bhop_settings[BHOP_INDEX_JUMP]);
	else
		PrintToServer("    Ticks : %2d           : %d (+%d = %d total)", bhop_settings_min[BHOP_INDEX_JUMP], bhop_settings[BHOP_INDEX_JUMP], bhop_settings[BHOP_INDEX_MIN], bhop_settings[BHOP_INDEX_JUMP] + bhop_settings[BHOP_INDEX_MIN]);
	PrintToServer("    Max   : %2d           : %d", bhop_settings_min[BHOP_INDEX_MAX], bhop_settings[BHOP_INDEX_MAX]);
	PrintToServer("    Air   : %2d           : %d", bhop_settings_min[BHOP_INDEX_AIR], bhop_settings[BHOP_INDEX_AIR]);
	PrintToServer("    Total : %2d           : %d", bhop_settings_min[BHOP_INDEX_TOTAL], bhop_settings[BHOP_INDEX_TOTAL]);
}

public Action lilac_ban_status(int args)
{
	int ban_type = 0;
	char tmp[24];

	PrintToServer("====[Little Anti-Cheat %s - Ban Status]====", PLUGIN_VERSION);
	PrintToServer("Checking ban plugins and third party plugins:");

	PrintToServer("Material-Admin:");
	PrintToServer("\tLoaded: %s", ((materialadmin_exist) ? "Yes" : "No"));
	PrintToServer("\tNative Exists: %s", ((NATIVE_EXISTS("MABanPlayer")) ? "Yes" : "No"));
	PrintToServer("\tConVar: lilac_materialadmin = %d", icvar[CVAR_MA]);

	PrintToServer("Sourcebans++:");
	PrintToServer("\tLoaded: %s", ((sourcebanspp_exist) ? "Yes" : "No"));
	PrintToServer("\tNative Exists: %s", ((NATIVE_EXISTS("SBPP_BanPlayer")) ? "Yes" : "No"));
	PrintToServer("\tConVar: lilac_sourcebans = %d", icvar[CVAR_SB]);

	PrintToServer("Sourcebans (Old):");
	PrintToServer("\tLoaded: %s", ((sourcebans_exist) ? "Yes" : "No"));
	PrintToServer("\tNative Exists: %s", ((NATIVE_EXISTS("SBBanPlayer")) ? "Yes" : "No"));
	PrintToServer("\tConVar: lilac_sourcebans = %d", icvar[CVAR_SB]);

	PrintToServer("SourceIRC:");
	PrintToServer("\tNative Exists: %s", ((NATIVE_EXISTS("IRC_MsgFlaggedChannels")) ? "Yes" : "No"));
	PrintToServer("\tConVar: lilac_sourceirc = %d", icvar[CVAR_SOURCEIRC]);
	if (icvar[CVAR_SOURCEIRC] && NATIVE_EXISTS("IRC_MsgFlaggedChannels"))
		IRC_MsgFlaggedChannels("lilac", "[LILAC] is active and logging to SourceIRC!");

	ban_type = ((icvar[CVAR_MA] && NATIVE_EXISTS("MABanPlayer")) ? 3 : 0);
	if (!ban_type)
		ban_type = ((icvar[CVAR_SB] && NATIVE_EXISTS("SBPP_BanPlayer")) ? 2 : 0);
	if (!ban_type)
		ban_type = (icvar[CVAR_SB] && NATIVE_EXISTS("SBBanPlayer"));

	switch (ban_type) {
	case 0: { strcopy(tmp, sizeof(tmp), "BaseBans"); }
	case 1: { strcopy(tmp, sizeof(tmp), "SourceBans (Old)"); }
	case 2: { strcopy(tmp, sizeof(tmp), "SourceBans++"); }
	case 3: { strcopy(tmp, sizeof(tmp), "Material-Admin"); }
	default: return Plugin_Handled;
	}

	PrintToServer("\nBanning will go though %s.\n", tmp);

	return Plugin_Handled;
}

public Action lilac_set_ban_length(int args)
{
	char feature[32], length[32];
	int index = -1;
	int time;

	if (args < 2) {
		PrintToServer("Error: Too few arguments.\nUsage: lilac_set_ban_length <cheat> <minutes>");
		PrintToServer("Example:\n\tlilac_set_ban_length bhop 15\n\t(Sets bhop ban to 15 minutes)");
		PrintToServer("\nIf ban length is -1, then the ban length will be the ConVar lilac_ban_length's value.");
		PrintToServer("\nPossible cheat arguments:");
		PrintToServer("\tlilac_set_ban_length angles <minutes>");
		PrintToServer("\tlilac_set_ban_length chatclear <minutes>");
		PrintToServer("\tlilac_set_ban_length convar <minutes>");
		PrintToServer("\tlilac_set_ban_length nolerp <minutes>");
		PrintToServer("\tlilac_set_ban_length bhop <minutes>");
		PrintToServer("\tlilac_set_ban_length aimbot <minutes>");
		PrintToServer("\tlilac_set_ban_length aimlock <minutes>");
		PrintToServer("\tlilac_set_ban_length antiduckdelay <minutes>");
		PrintToServer("\tlilac_set_ban_length noisemaker <minutes>");
		PrintToServer("\tlilac_set_ban_length macro <minutes>");
		PrintToServer("\tlilac_set_ban_length name <minutes>\n");

		return Plugin_Handled;
	}

	GetCmdArg(1, feature, sizeof(feature));

	if (StrEqual(feature, "angles", false) || StrEqual(feature, "angle", false)) {
		index = CHEAT_ANGLES;
	}
	else if (StrEqual(feature, "chat", false) || StrEqual(feature, "chatclear", false)) {
		index = CHEAT_CHATCLEAR;
	}
	else if (StrEqual(feature, "convar", false) || StrEqual(feature, "cvar", false)) {
		index = CHEAT_CONVAR;
	}
	else if (StrEqual(feature, "nolerp", false)) {
		index = CHEAT_NOLERP;
	}
	else if (StrEqual(feature, "bhop", false) || StrEqual(feature, "bunnyhop", false)) {
		index = CHEAT_BHOP;
	}
	else if (StrEqual(feature, "aimbot", false) || StrEqual(feature, "aim", false)) {
		index = CHEAT_AIMBOT;
	}
	else if (StrEqual(feature, "aimlock", false) || StrEqual(feature, "lock", false)) {
		index = CHEAT_AIMLOCK;
	}
	/* ( @~@) Bruh.... This is... B R U H */
	else if (StrEqual(feature, "duck", false) || StrEqual(feature, "crouch", false)
		|| StrEqual(feature, "antiduck", false) || StrEqual(feature, "antiduckdelay", false)
		|| StrEqual(feature, "fastduck", false)) {
		index = CHEAT_ANTI_DUCK_DELAY;
	}
	else if (StrEqual(feature, "noisemaker", false) || StrEqual(feature, "noise", false)) {
		index = CHEAT_NOISEMAKER_SPAM;
	}
	else if (StrEqual(feature, "macro", false)) {
		index = CHEAT_MACRO;
	}
	else if (StrEqual(feature, "name", false) || StrEqual(feature, "filter", false)) {
		index = CHEAT_NEWLINE_NAME;
	}
	else {
		PrintToServer("Error: Unknown cheat feature \"%s\"", feature);
		return Plugin_Handled;
	}

	GetCmdArg(2, length, sizeof(length));
	time = StringToInt(length, 10);

	/* Macro exception. */
	if (index == CHEAT_MACRO) {
		if (time > 60)
			time = 60;
		else if (time < 15)
			time = 15;
	}
	else if (time < -1)
		time = -1;

	ban_length_overwrite[index] = time;

	/* Todo: Add message showing new times? Like the bhop set command :) */

	return Plugin_Handled;
}

public Action lilac_date_list(int args)
{
	PrintToServer("=======[Lilac Date Formatting]=======");
	PrintToServer("Manual formatting:");
	PrintToServer("\t{raw} = Skips the special formatting listed here");
	PrintToServer("\t        and lets you insert your own formatting");
	PrintToServer("\t        (see: http://www.cplusplus.com/reference/ctime/strftime/).");
	PrintToServer("Example:\n\t{raw}%%Y/%%m/%%d %%H:%%M:%%S");
	PrintToServer("Dates:");
	PrintToServer("\t{year}    = Numerical year  (2020).");
	PrintToServer("\t{month}   = Numerical month   (12).");
	PrintToServer("\t{day}     = Numerical day     (28).");
	PrintToServer("Time:");
	PrintToServer("\t{hour}    = 24 hour format.");
	PrintToServer("\t{hours}   = 24 hour format.");
	PrintToServer("\t{24hour}  = 24 hour format.");
	PrintToServer("\t{24hours} = 24 hour format.");
	PrintToServer("\t{12hour}  = 12 hour format.");
	PrintToServer("\t{12hours} = 12 hour format.");
	PrintToServer("\t{pm}      = Insert AM/PM.");
	PrintToServer("\t{am}      = Insert AM/PM.");
	PrintToServer("\t{minute}  = Minute.");
	PrintToServer("\t{minutes} = Minute.");
	PrintToServer("\t{second}  = Second.");
	PrintToServer("\t{seconds} = Second.");
	PrintToServer("Using flags example: {year}/{month}/{day} {hour}:{minute}:{second}");

	return Plugin_Continue;
}

public void cvar_change(ConVar convar, const char[] oldValue, const char[] newValue)
{
	char cvarname[64];
	char testdate[512];

	/* Thanks to MAGNAT2645 for informing me I could do this! */
	if (convar == hcvar[CVAR_ENABLE]) {
		icvar[CVAR_ENABLE] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_WELCOME]) {
		icvar[CVAR_WELCOME] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_SB]) {
		icvar[CVAR_SB] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_MA]) {
		icvar[CVAR_MA] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_SOURCEIRC]) {
		icvar[CVAR_SOURCEIRC] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_LOG]) {
		icvar[CVAR_LOG] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_LOG_EXTRA]) {
		icvar[CVAR_LOG_EXTRA] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_LOG_MISC]) {
		icvar[CVAR_LOG_MISC] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_LOG_DATE]) {
		lilac_setup_date_format(newValue);
		
		FormatTime(testdate, sizeof(testdate), dateformat, GetTime());
		PrintToServer("Date Format Preview: %s", testdate);
	}
	else if (convar == hcvar[CVAR_BAN]) {
		icvar[CVAR_BAN] = StringToInt(newValue, 10);

		if (!icvar[CVAR_BAN])
			PrintToServer("[Little Anti-Cheat %s] WARNING: 'lilac_ban' has been set to 0, banning of cheaters has been disabled.", PLUGIN_VERSION);
	}
	else if (convar == hcvar[CVAR_BAN_LENGTH]) {
		icvar[CVAR_BAN_LENGTH] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_BAN_LANGUAGE]) {
		icvar[CVAR_BAN_LANGUAGE] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_CHEAT_WARN]) {
		icvar[CVAR_CHEAT_WARN] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_ANGLES]) {
		icvar[CVAR_ANGLES] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_PATCH_ANGLES]) {
		icvar[CVAR_PATCH_ANGLES] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_CHAT]) {
		icvar[CVAR_CHAT] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_CONVAR]) {
		icvar[CVAR_CONVAR] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_NOLERP]) {
		icvar[CVAR_NOLERP] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_BHOP]) {
		icvar[CVAR_BHOP] = StringToInt(newValue, 10);
		lilac_bhop_set_preset();
	}
	else if (convar == hcvar[CVAR_AIMBOT]) {
		icvar[CVAR_AIMBOT] = StringToInt(newValue, 10);
		
		if (icvar[CVAR_AIMBOT] > 1 &&
			icvar[CVAR_AIMBOT] < AIMBOT_BAN_MIN)
			icvar[CVAR_AIMBOT] = 5;
	}
	else if (convar == hcvar[CVAR_AIMBOT_AUTOSHOOT]) {
		icvar[CVAR_AIMBOT_AUTOSHOOT] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_AIMLOCK]) {
		icvar[CVAR_AIMLOCK] = StringToInt(newValue, 10);
		
		if (icvar[CVAR_AIMLOCK] > 1
			&& icvar[CVAR_AIMLOCK] < AIMLOCK_BAN_MIN)
			icvar[CVAR_AIMLOCK] = 5;
	}
	else if (convar == hcvar[CVAR_AIMLOCK_LIGHT]) {
		icvar[CVAR_AIMLOCK_LIGHT] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_ANTI_DUCK_DELAY]) {
		icvar[CVAR_ANTI_DUCK_DELAY] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_NOISEMAKER_SPAM]) {
		icvar[CVAR_NOISEMAKER_SPAM] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_BACKTRACK_PATCH]) {
		icvar[CVAR_BACKTRACK_PATCH] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_BACKTRACK_TOLERANCE]) {
		icvar[CVAR_BACKTRACK_TOLERANCE] = StringToInt(newValue, 10);
		
		if (icvar[CVAR_BACKTRACK_TOLERANCE] > 2)
			PrintToServer("[Little Anti-Cheat %s] WARNING: It is not recommeded to set backtrack tolerance above 2, only do this if you understand what this means.", PLUGIN_VERSION);
	}
	else if (convar == hcvar[CVAR_MAX_PING]) {
		icvar[CVAR_MAX_PING] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_MAX_PING_SPEC]) {
		icvar[CVAR_MAX_PING_SPEC] = StringToInt(newValue, 10);
		
		if (icvar[CVAR_MAX_PING_SPEC] < 30)
			icvar[CVAR_MAX_PING_SPEC] = 0;
	}
	else if (convar == hcvar[CVAR_MAX_LERP]) {
		icvar[CVAR_MAX_LERP] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_MACRO]) {
		icvar[CVAR_MACRO] = StringToInt(newValue, 10);

		if (icvar[CVAR_MACRO] > 0)
			PrintToServer("[Little Anti-Cheat %s] WARNING: It's recommended to use log-only method for now.", PLUGIN_VERSION);

		/* Settings changed, reset counters. */
		for (int i = 1; i <= MaxClients; i++)
			lilac_macro_reset_client(i);
	}
	else if (convar == hcvar[CVAR_MACRO_WARNING]) {
		icvar[CVAR_MACRO_WARNING] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_MACRO_DEAL_METHOD]) {
		icvar[CVAR_MACRO_DEAL_METHOD] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_MACRO_MODE]) {
		icvar[CVAR_MACRO_MODE] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_FILTER_NAME]) {
		icvar[CVAR_FILTER_NAME] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_FILTER_CHAT]) {
		icvar[CVAR_FILTER_CHAT] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_LOSS_FIX]) {
		icvar[CVAR_LOSS_FIX] = StringToInt(newValue, 10);
	}
	else if (convar == hcvar[CVAR_AUTO_UPDATE]) {
		icvar[CVAR_AUTO_UPDATE] = StringToInt(newValue, 10);
		
		lilac_update_url();
	}
	else if (convar == hcvar[CVAR_DATABASE]) {
		strcopy(db_name, sizeof(db_name), newValue);
	}
	else {
		convar.GetName(cvarname, sizeof(cvarname));
		
		if (StrEqual(cvarname, "sv_autobunnyhopping", false)) {
			force_disable_bhop = StringToInt(newValue, 10);
		}
		else if (StrEqual(cvarname, "sv_maxupdaterate", false)) {
			/* NoLerp checks need to know this value. */
			lilac_lerp_maxupdaterate_changed(StringToInt(newValue));

			/* Changing this convar mid-game can cause false positives.
			 * Ignore players already in-game. */
			for (int i = 1; i <= MaxClients; i++)
				lilac_lerp_ignore_nolerp_client(i);
		}
		else if (StrEqual(cvarname, "sv_client_min_interp_ratio", false)) {
			lilac_lerp_ratio_changed(StringToInt(newValue));
		}
		else if (StrEqual(cvarname, "sv_client_max_interp_ratio", false)) {
			lilac_lerp_ratio_changed(StringToInt(newValue));
		}
		else if (StrEqual(cvarname, "sv_cheats", false)) {
			sv_cheats = StringToInt(newValue);
			
			/* Delay convar checks for 30 seconds. */
			time_sv_cheats = GetTime() + QUERY_TIMEOUT;
		}
	}
}

static int bclamp(int n, int idx)
{
	return ((n < bhop_settings_min[idx]) ? bhop_settings_min[idx] : n);
}

static void lilac_bhop_set_preset()
{
	int mode = intabs(icvar[CVAR_BHOP]);

	switch (mode) {
	/* Backwards compatibility, mode 1 & 2 don't exist anymore.
	 * If a config is already set to use these, change mode to medium. */
	case BHOP_MODE_RESERVED_1, BHOP_MODE_RESERVED_2: {
		PrintToServer("[Lilac] Warning: Bhop mode 1 & 2 has been disabled, setting Bhop mode to %d (Medium).",
			BHOP_MODE_MEDIUM);
		hcvar[CVAR_BHOP].SetInt(BHOP_MODE_MEDIUM, false, false);
	}
	case BHOP_MODE_LOW, BHOP_MODE_CUSTOM: {
		/* Can't do switch fall-through in SourcePawn.
		 * Makes me miss C :( */
		if (mode == BHOP_MODE_CUSTOM) {
			PrintToServer("[Lilac] WARNING: DO NOT USE CUSTOM BHOP MODE UNLESS YOU KNOW WHAT YOU ARE DOING!");
			PrintToServer("[Lilac] ВНИМАНИЕ: НЕ ИСПОЛЬЗУЙТЕ ПОЛЬЗОВАТЕЛЬСКИЙ РЕЖИМ BHOP, ЕСЛИ ВЫ НЕ ЗНАЕТЕ, ЧТО ВЫ ДЕЛАЕТЕ!");
		}

		/* Most of these clamps aren't necessary,
		 * but in-case I change the presets in the future,
		 * these are nice :) */
		bhop_settings[BHOP_INDEX_MIN] = bclamp(10, BHOP_INDEX_MIN);
		bhop_settings[BHOP_INDEX_JUMP] = 5;
		bhop_settings[BHOP_INDEX_MAX] = bclamp(25, BHOP_INDEX_MAX);
		bhop_settings[BHOP_INDEX_AIR] = RoundToCeil(tick_rate * 0.3);
		bhop_settings[BHOP_INDEX_TOTAL] = bclamp(5, BHOP_INDEX_TOTAL);
	}
	case BHOP_MODE_MEDIUM: {
		bhop_settings[BHOP_INDEX_MIN] = bclamp(7, BHOP_INDEX_MIN);
		bhop_settings[BHOP_INDEX_JUMP] = -1;
		bhop_settings[BHOP_INDEX_MAX] = bclamp(20, BHOP_INDEX_MAX);
		bhop_settings[BHOP_INDEX_AIR] = RoundToCeil(tick_rate * 0.3);
		bhop_settings[BHOP_INDEX_TOTAL] = bclamp(3, BHOP_INDEX_TOTAL);
	}
	case BHOP_MODE_HIGH: {
		bhop_settings[BHOP_INDEX_MIN] = bclamp(5, BHOP_INDEX_MIN);
		bhop_settings[BHOP_INDEX_JUMP] = 8; /* 5 for SMAC bypass, and 3 as buffer. */
		bhop_settings[BHOP_INDEX_MAX] = bclamp(20, BHOP_INDEX_MAX);
		bhop_settings[BHOP_INDEX_AIR] = RoundToCeil(tick_rate * 0.3);
		bhop_settings[BHOP_INDEX_TOTAL] = bclamp(1, BHOP_INDEX_TOTAL);
	}
	}

	print_current_bhop_settings();
}

static void lilac_setup_date_format(const char []format)
{
	strcopy(dateformat, sizeof(dateformat), format);

	if (ReplaceString(dateformat, sizeof(dateformat), "{raw}", "", false))
		return;

	ReplaceString(dateformat, sizeof(dateformat), "%%", "%%%%", false);

	ReplaceString(dateformat, sizeof(dateformat), "{year}", "%Y", false);
	ReplaceString(dateformat, sizeof(dateformat), "{month}", "%m", false);
	ReplaceString(dateformat, sizeof(dateformat), "{day}", "%d", false);

	ReplaceString(dateformat, sizeof(dateformat), "{hour}", "%H", false);
	ReplaceString(dateformat, sizeof(dateformat), "{hours}", "%H", false);
	ReplaceString(dateformat, sizeof(dateformat), "{24hour}", "%H", false);
	ReplaceString(dateformat, sizeof(dateformat), "{24hours}", "%H", false);
	ReplaceString(dateformat, sizeof(dateformat), "{12hour}", "%I", false);
	ReplaceString(dateformat, sizeof(dateformat), "{12hours}", "%I", false);
	ReplaceString(dateformat, sizeof(dateformat), "{pm}", "%p", false);
	ReplaceString(dateformat, sizeof(dateformat), "{am}", "%p", false);

	ReplaceString(dateformat, sizeof(dateformat), "{minute}", "%M", false);
	ReplaceString(dateformat, sizeof(dateformat), "{minutes}", "%M", false);
	ReplaceString(dateformat, sizeof(dateformat), "{second}", "%S", false);
	ReplaceString(dateformat, sizeof(dateformat), "{seconds}", "%S", false);
}


================================================
FILE: scripting/lilac/lilac_convar.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/


/* Basic query list. */
static char query_list[][] = {
	"sv_cheats",
	"r_drawothermodels",
	"mat_wireframe",
	"snd_show",
	"snd_visualize",
	"mat_proxy",
	"r_drawmodelstatsoverlay",
	"r_shadowwireframe",
	"r_showenvcubemap",
	"r_drawrenderboxes",
	"r_modelwireframedecal"
};

static int query_index[MAXPLAYERS + 1];
static int query_failed[MAXPLAYERS + 1];

void lilac_convar_reset_client(int client)
{
	query_index[client] = 0;
	query_failed[client] = 0;
}

public Action timer_query(Handle timer)
{
	if (!icvar[CVAR_ENABLE] || !icvar[CVAR_CONVAR])
		return Plugin_Continue;

	/* sv_cheats recently changed or is set to 1, abort. */
	if (GetTime() < time_sv_cheats || sv_cheats)
		return Plugin_Continue;

	for (int i = 1; i <= MaxClients; i++) {
		if (!is_player_valid(i) || IsFakeClient(i))
			continue;

		/* Player recently joined, wait before querying. */
		if (GetClientTime(i) < 60.0)
			continue;

		/* Don't query already banned players. */
		if (playerinfo_banned_flags[i][CHEAT_CONVAR])
			continue;

		/* Only increments query index if the player
		 * has responded to the last one. */
		if (!query_failed[i]) {
			if (++query_index[i] >= 11)
				query_index[i] = 0;
		}

		QueryClientConVar(i, query_list[query_index[i]], query_reply, 0);

		if (++query_failed[i] > QUERY_MAX_FAILURES) {
			if (icvar[CVAR_LOG_MISC]) {
				lilac_log_setup_client(i);
				Format(line_buffer, sizeof(line_buffer),
					"%s was kicked for failing to respond to %d queries in %.0f seconds.",
					line_buffer, QUERY_MAX_FAILURES,
					QUERY_TIMER * QUERY_MAX_FAILURES);

				lilac_log(true);

				if (icvar[CVAR_LOG_EXTRA] == 2)
					lilac_log_extra(i);
			}
			database_log(i, "cvar_query_failure", DATABASE_KICK, float(QUERY_MAX_FAILURES), QUERY_TIMER * QUERY_MAX_FAILURES);

			KickClient(i, "[Lilac] %T", "kick_query_failure", i);
		}
	}

	return Plugin_Continue;
}

public void query_reply(QueryCookie cookie, int client, ConVarQueryResult result,
			const char[] cvarName, const char[] cvarValue, any value)
{
	/* Player NEEDS to answer the query. */
	if (result != ConVarQuery_Okay)
		return;

	/* Client did respond to the query request, move on to the next convar. */
	query_failed[client] = 0;

	/* Any response the server may recieve may also be faulty, ignore. */
	if (GetTime() < time_sv_cheats || sv_cheats)
		return;

	/* Already banned. */
	if (playerinfo_banned_flags[client][CHEAT_CONVAR])
		return;

	int val = StringToInt(cvarValue);

	/* Check for invalid convar responses.
	 * Other than drawothermodels, a value of non-zero is invalid. */
	if (StrEqual("r_drawothermodels", cvarName, false) && val == 1)
		return;
	else if (val == 0)
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_CONVAR) == false)
		return;

	lilac_forward_client_cheat(client, CHEAT_CONVAR);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s was detected and banned for an invalid ConVar (%s %s).",
			line_buffer, cvarName, cvarValue);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}
	database_log(client, "cvar_invalid", DATABASE_BAN);

	playerinfo_banned_flags[client][CHEAT_CONVAR] = true;
	lilac_ban_client(client, CHEAT_CONVAR);
}


================================================
FILE: scripting/lilac/lilac_database.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2021-2023 azalty
	Copyright (C) 2023-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

void Database_OnConfigExecuted()
{
	if (lil_db)
		return;
	
	static bool first_load = true;
	
	/* Since db_name is only updated when the config name is CHANGED,
	 * we need to retrieve it on the first load, since the execution
	 * of the config file doesn't count like a change. */
	if (first_load) {
		hcvar[CVAR_DATABASE].GetString(db_name, sizeof(db_name));
		first_load = false;
	}
	
	if (db_name[0] == '\0' || IsCharSpace(db_name[0])) /* The config name is empty */
		return;
	
	if (!SQL_CheckConfig(db_name)) {
		LogError("Database config '%s' doesn't exist in databases.cfg", db_name);
		return;
	}
	Database.Connect(OnDatabaseConnected, db_name);
}

public void OnDatabaseConnected(Database db, const char[] error, any data)
{
	if (!db) {
		LogError("Couldn't connect to the database. Please verify your config.");
		return;
	}
	lil_db = db;

	InitDatabase();
}

void InitDatabase()
{
	/* SQLite syntax, but seems valid for MySQL too */
	lil_db.Query(OnDatabaseInit, "CREATE TABLE IF NOT EXISTS lilac_detections("
		... "name varchar(128) NOT NULL, " /* Honestly, you can deal with less bytes. 32 is fine too, but since you shouldn't have a ton of detections, that should be okay. */
		... "steamid varchar(32) NOT NULL, "
		... "ip varchar(16) NOT NULL, "
		... "cheat varchar(50) NOT NULL, "
		... "timestamp INTEGER NOT NULL, "
		... "detection INTEGER NOT NULL, "
		... "pos1 FLOAT NOT NULL, "
		... "pos2 FLOAT NOT NULL, "
		... "pos3 FLOAT NOT NULL, "
		... "ang1 FLOAT NOT NULL, "
		... "ang2 FLOAT NOT NULL, "
		... "ang3 FLOAT NOT NULL, "
		... "map varchar(128) NOT NULL, "
		... "team INTEGER NOT NULL, "
		... "weapon varchar(64) NOT NULL, "
		... "data1 FLOAT NOT NULL, "
		... "data2 FLOAT NOT NULL, "
		... "latency_inc FLOAT NOT NULL, "
		... "latency_out FLOAT NOT NULL, "
		... "loss_inc FLOAT NOT NULL, "
		... "loss_out FLOAT NOT NULL, "
		... "choke_inc FLOAT NOT NULL, "
		... "choke_out FLOAT NOT NULL, "
		... "connection_ticktime FLOAT NOT NULL, "
		... "game_ticktime FLOAT NOT NULL, "
		... "lilac_version varchar(20) NOT NULL)");
	
	/* Sets the right charset to store player names, in case you don't know how to configure your DB (lol).
	 * This only sets the connection charset. */
	lil_db.SetCharset("utf8mb4");
}

public void OnDatabaseInit(Database db, DBResultSet results, const char[] error, any data)
{
	if (!results) {
		LogError("Database initation query failed (%s)", error);
		delete lil_db;
	}
}

/* Logs a row to the LilAC's database if it exists.
 * @param client      Client index.
 * @param cheat       Cheat name.
 * @param detection   Detection number or sanction: DATABASE_BAN, DATABASE_KICK, DATABASE_LOG_ONLY
 * @param data1       Additional data to pass as a float. You can convert integers to float.
 * @param data2       Additional data to pass as a float. You can convert integers to float. */
void database_log(int client, char[] cheat, int detection=DATABASE_BAN, float data1=0.0, float data2=0.0)
{
	if (!lil_db)
		return;

	char steamid[32], ip[16], map[128], weapon[64];
	float pos[3], ang[3];

	char name[MAX_NAME_LENGTH];
	char safe_name[(sizeof(name)*2)+1];
	if (!GetClientName(client, name, sizeof(name)))
		strcopy(safe_name, sizeof(safe_name), "<no name>");
	else {
		TrimString(name);
		lil_db.Escape(name, safe_name, sizeof(safe_name));
		if (strlen(safe_name) >= 128) /* prevents exploits: don't exceed 127 characters else somes names could break the query */
			strcopy(safe_name, sizeof(safe_name), "<no name>");
	}

	GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid), true);
	GetClientIP(client, ip, sizeof(ip), true);

	GetClientAbsOrigin(client, pos);
	GetCurrentMap(map, sizeof(map));
	GetClientWeapon(client, weapon, sizeof(weapon));

	get_player_log_angles(client, 0, true, ang);

	FormatEx(sql_buffer, sizeof(sql_buffer), "INSERT INTO lilac_detections("
		... "name, "
		... "steamid, "
		... "ip, "
		... "cheat, "
		... "timestamp, "
		... "detection, " /* detection '0' means a ban, '-1' means a kick. Everything else is a detection number. */
		... "pos1, "
		... "pos2, "
		... "pos3, "
		... "ang1, "
		... "ang2, "
		... "ang3, "
		... "map, "
		... "team, "
		... "weapon, "
		... "data1, "
		... "data2, "
		... "latency_inc, "
		... "latency_out, "
		... "loss_inc, "
		... "loss_out, "
		... "choke_inc, "
		... "choke_out, "
		... "connection_ticktime, "
		... "game_ticktime, "
		... "lilac_version) "
		... "VALUES("
		... "'%s', "
		... "'%s', "
		... "'%s', "
		... "'%s', "
		... "'%i', "
		... "'%i', "
		... "'%.0f', "
		... "'%.0f', "
		... "'%.0f', "
		... "'%.5f', "
		... "'%.5f', "
		... "'%.5f', "
		... "'%s', "
		... "'%i', "
		... "'%s', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%f', "
		... "'%s')",
		safe_name,
		steamid,
		ip,
		cheat,
		GetTime(),
		detection,
		pos[0],
		pos[1],
		pos[2],
		ang[0],
		ang[1],
		ang[2],
		map,
		GetClientTeam(client),
		weapon,
		data1,
		data2,
		GetClientAvgLatency(client, NetFlow_Incoming),
		GetClientAvgLatency(client, NetFlow_Outgoing),
		GetClientAvgLoss(client, NetFlow_Incoming),
		GetClientAvgLoss(client, NetFlow_Outgoing),
		GetClientAvgChoke(client, NetFlow_Incoming),
		GetClientAvgChoke(client, NetFlow_Outgoing),
		GetClientTime(client),
		GetGameTime(),
		PLUGIN_VERSION);
	lil_db.Query(OnDetectionInserted, sql_buffer);
}

public void OnDetectionInserted(Database db, DBResultSet results, const char[] error, any data)
{
	if (!results)
		LogError("Detection insertion query failed (%s)", error);
}


================================================
FILE: scripting/lilac/lilac_globals.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#define NATIVE_EXISTS(%0)   (GetFeatureStatus(FeatureType_Native, %0) == FeatureStatus_Available)
#define UPDATE_URL          "https://raw.githubusercontent.com/J-Tanzanite/Little-Anti-Cheat/master/updatefile.txt"

#define CMD_LENGTH   330

#define GAME_UNKNOWN   0
#define GAME_TF2       1
#define GAME_CSS       2
#define GAME_CSGO      3
#define GAME_DODS      4
#define GAME_L4D2      5
#define GAME_L4D       6

/* In case anyone wants to change this later on in a pull request or whatever,
 * DON'T DON'T DON'T DON'T DON'T DON'T DON'T DON'T DON'T DON'T DON'T!!!
 * ...  DON'T...
 * These values cannot be changed due to forwards,
 *     changing them will cause issues for other plugins.
 * You can add new stuff, but not change the number of anything here. */
#define CHEAT_ANGLES             0
#define CHEAT_CHATCLEAR          1
#define CHEAT_CONVAR             2
#define CHEAT_NOLERP             3
#define CHEAT_BHOP               4
#define CHEAT_AIMBOT             5
#define CHEAT_AIMLOCK            6
#define CHEAT_ANTI_DUCK_DELAY    7
#define CHEAT_NOISEMAKER_SPAM    8
#define CHEAT_MACRO              9 /* Macros aren't actually cheats, but are forwarded as such. */
#define CHEAT_NEWLINE_NAME      10
#define CHEAT_MAX               11

#define CVAR_ENABLE                 0
#define CVAR_WELCOME                1
#define CVAR_SB                     2
#define CVAR_MA                     3
#define CVAR_LOG                    4
#define CVAR_LOG_EXTRA              5
#define CVAR_LOG_MISC               6
#define CVAR_LOG_DATE               7
#define CVAR_BAN                    8
#define CVAR_BAN_LENGTH             9
#define CVAR_BAN_LANGUAGE          10
#define CVAR_CHEAT_WARN            11
#define CVAR_ANGLES                12
#define CVAR_PATCH_ANGLES          13
#define CVAR_CHAT                  14
#define CVAR_CONVAR                15
#define CVAR_NOLERP                16
#define CVAR_BHOP                  17
#define CVAR_AIMBOT                18
#define CVAR_AIMBOT_AUTOSHOOT      19
#define CVAR_AIMLOCK               20
#define CVAR_AIMLOCK_LIGHT         21
#define CVAR_ANTI_DUCK_DELAY       22
#define CVAR_NOISEMAKER_SPAM       23
#define CVAR_BACKTRACK_PATCH       24
#define CVAR_BACKTRACK_TOLERANCE   25
#define CVAR_MAX_PING              26
#define CVAR_MAX_PING_SPEC         27
#define CVAR_MAX_LERP              28
#define CVAR_MACRO                 29
#define CVAR_MACRO_WARNING         30
#define CVAR_MACRO_DEAL_METHOD     31
#define CVAR_MACRO_MODE            32
#define CVAR_FILTER_NAME           33
#define CVAR_FILTER_CHAT           34
#define CVAR_LOSS_FIX              35
#define CVAR_AUTO_UPDATE           36
#define CVAR_SOURCEIRC             37
#define CVAR_DATABASE              38
#define CVAR_MAX                   39

#define BHOP_INDEX_MIN     0
#define BHOP_INDEX_JUMP    1
#define BHOP_INDEX_MAX     2
#define BHOP_INDEX_TOTAL   3
#define BHOP_INDEX_AIR     4
#define BHOP_MAX           5

#define BHOP_MODE_DISABLED     0
#define BHOP_MODE_RESERVED_1   1
#define BHOP_MODE_RESERVED_2   2
#define BHOP_MODE_CUSTOM       3
#define BHOP_MODE_LOW          4
#define BHOP_MODE_MEDIUM       5
#define BHOP_MODE_HIGH         6

#define NOISEMAKER_TYPE_NONE        0
#define NOISEMAKER_TYPE_LIMITED     1
#define NOISEMAKER_TYPE_UNLIMITED   2

#define MACRO_LOG_LENGTH   200

#define MACRO_AUTOJUMP    0
#define MACRO_AUTOSHOOT   1
#define MACRO_ARRAY       2

#define ACTION_SHOT   1

#define QUERY_MAX_FAILURES   24
#define QUERY_TIMEOUT        30
#define QUERY_TIMER          5.0

#define AIMLOCK_BAN_MIN   5

#define AIMBOT_BAN_MIN           5
#define AIMBOT_MAX_TOTAL_DELTA   (180.0 * 2.5)
#define AIMBOT_FLAG_REPEAT       (1 << 0)
#define AIMBOT_FLAG_AUTOSHOOT    (1 << 1)
#define AIMBOT_FLAG_SNAP         (1 << 2)
#define AIMBOT_FLAG_SNAP2        (1 << 3)

#define STRFLAG_NEWLINE          (1 << 0) /* Carriage return or Newline. */
#define STRFLAG_WIDE_CHAR_SPAM   (1 << 1) /* Lots of wide character spam. */

#define DATABASE_BAN 0
#define DATABASE_KICK -1
#define DATABASE_LOG_ONLY -2

#define PLUGIN_NAME      "[Lilac] Little Anti-Cheat"
#define PLUGIN_AUTHOR    "J_Tanzanite"
#define PLUGIN_DESC      "An opensource Anti-Cheat"
#define PLUGIN_VERSION   "1.7.4"
#define PLUGIN_URL       "https://github.com/J-Tanzanite/Little-Anti-Cheat"

/* Convars. */
Convar hcvar[CVAR_MAX]; /* ConVar = built in SourceMod  |  Convar = kidfearless's convar_class */
int icvar[CVAR_MAX];
int sv_cheats = 0;
int time_sv_cheats = 0;
int force_disable_bhop = 0;

/* Banlength overwrite. */
int ban_length_overwrite[CHEAT_MAX];

/* Database. */
Database lil_db;
char sql_buffer[1500]; /* It's probably bigger than what you need, but better be safe than sorry I guess. */
char db_name[64]; /* Database config name from hcvar[CVAR_DATABASE]. */

/* Misc. */
int ggame;
int tick_rate;
int macro_max;
int bhop_settings[BHOP_MAX];
int bhop_settings_min[BHOP_MAX];

char line_buffer[2048];
char dateformat[512] = "%Y/%m/%d %H:%M:%S";
char log_file[PLATFORM_MAX_PATH];
float max_angles[3] = {89.01, 0.0, 50.01};
Handle forwardhandle = INVALID_HANDLE;
Handle forwardhandleban = INVALID_HANDLE;
Handle forwardhandleallow = INVALID_HANDLE;

/* External plugins. */
bool sourcebans_exist = false;
bool sourcebanspp_exist = false;
bool materialadmin_exist = false;

/* Logging.
 * Todo: Might wanna move a lot of this variables to
 * their own files if they are only used there.
 * Just so the code gets a lot cleaner. */
int playerinfo_index[MAXPLAYERS + 1];
int playerinfo_buttons[MAXPLAYERS + 1][CMD_LENGTH];
int playerinfo_actions[MAXPLAYERS + 1][CMD_LENGTH];
int playerinfo_aimlock_sus[MAXPLAYERS + 1];
int playerinfo_aimlock[MAXPLAYERS + 1];
float playerinfo_time_bumpercart[MAXPLAYERS + 1];
float playerinfo_time_teleported[MAXPLAYERS + 1];
float playerinfo_time_aimlock[MAXPLAYERS + 1];
float playerinfo_time_process_aimlock[MAXPLAYERS + 1];
float playerinfo_angles[MAXPLAYERS + 1][CMD_LENGTH][3];
float playerinfo_time_usercmd[MAXPLAYERS + 1][CMD_LENGTH];
float playerinfo_time_forward[MAXPLAYERS + 1][CHEAT_MAX];
bool playerinfo_banned_flags[MAXPLAYERS + 1][CHEAT_MAX];


/* Forward declarations so we don't need third-party include files. */

#define MA_BAN_STEAM  1

native Function IRC_MsgFlaggedChannels(const char[] flag, const char[] format, any:...);
native Function MABanPlayer(int iClient, int iTarget, int iType, int iTime, char[] sReason);
native Function SBBanPlayer(int client, int target, int time, const char[] reason);
native Function SBPP_BanPlayer(int iAdmin, int iTarget, int iTime, const char[] sReason);
native Function Updater_AddPlugin(const char[] url);
native Function Updater_RemovePlugin();

================================================
FILE: scripting/lilac/lilac_lerp.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

static float min_lerp_possible = 0.0;
static bool ignore_nolerp[MAXPLAYERS + 1];
static bool ignore_nolerp_all = false;

void lilac_lerp_reset_client(int client)
{
	ignore_nolerp[client] = false;
}

void lilac_lerp_ignore_nolerp_client(int client)
{
	ignore_nolerp[client] = true;
}

void lilac_lerp_ratio_changed(int value)
{
	/* Permamently disable nolerp checks.
	 * Yes, this is a little harsh, but if servers allow
	 * any interp ratio, they are unlikely to change
	 * it back to any restrictive value. */
	if (value < 1)
		ignore_nolerp_all = true;
}

void lilac_lerp_maxupdaterate_changed(int value)
{
	if (ignore_nolerp_all)
		return;

	min_lerp_possible = ((value > 0) ? 1.0 / float(value) : 0.0);
}

public Action timer_check_lerp(Handle timer)
{
	if (!icvar[CVAR_ENABLE])
		return Plugin_Continue;

	for (int i = 1; i <= MaxClients; i++) {
		if (!is_player_valid(i) || IsFakeClient(i))
			continue;

		float lerp = GetEntPropFloat(i, Prop_Data, "m_fLerpTime");

		if (lerp * 1000.0 > float(icvar[CVAR_MAX_LERP]) && icvar[CVAR_MAX_LERP] >= 105) {
			detected_lerp_exploit(i, lerp);
			continue;
		}

		if (!icvar[CVAR_NOLERP]
			|| ignore_nolerp_all
			|| ignore_nolerp[i]
			|| playerinfo_banned_flags[i][CHEAT_NOLERP]
			|| min_lerp_possible < 0.005) /* Minvalue invalid or too low. */
			continue;

		if (lerp > min_lerp_possible * 0.95 /* buffer */)
			continue;

		detected_nolerp(i, lerp);
	}

	return Plugin_Continue;
}

static void detected_lerp_exploit(int client, float lerp)
{
	if (icvar[CVAR_LOG_MISC]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s was kicked for exploiting interpolation (%.3fms / %dms max).",
			line_buffer, lerp * 1000.0, icvar[CVAR_MAX_LERP]);

		lilac_log(true);
		if (icvar[CVAR_LOG_EXTRA] == 2)
			lilac_log_extra(client);
	}
	database_log(client, "lerp_exploit", DATABASE_KICK, lerp * 1000.0, float(icvar[CVAR_MAX_LERP]));

	KickClient(client, "[Lilac] %T", "kick_interp_exploit", client,
		lerp * 1000.0, icvar[CVAR_MAX_LERP],
		float(icvar[CVAR_MAX_LERP]) / 999.9);
}

static void detected_nolerp(int client, float lerp)
{
	if (lilac_forward_allow_cheat_detection(client, CHEAT_NOLERP) == false)
		return;

	playerinfo_banned_flags[client][CHEAT_NOLERP] = true;

	lilac_forward_client_cheat(client, CHEAT_NOLERP);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer), "%s was detected and banned for NoLerp (%fms).",
			line_buffer, lerp * 1000.0);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}
	database_log(client, "nolerp", DATABASE_BAN, lerp * 1000.0);

	lilac_ban_client(client, CHEAT_NOLERP);
}


================================================
FILE: scripting/lilac/lilac_macro.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

static int macro_history[MAXPLAYERS + 1][MACRO_ARRAY][MACRO_LOG_LENGTH];
static int macro_detected[MAXPLAYERS + 1][MACRO_ARRAY];

void lilac_macro_reset_client(int client)
{
	for (int i = 0; i < MACRO_ARRAY; i++) {
		macro_detected[client][i] = 0;
		lilac_macro_reset_client_history(client, i);
	}
}

static void lilac_macro_reset_client_history(int client, int type)
{
	if (type < 0 || type >= MACRO_ARRAY)
		return;

	for (int i = 0; i < MACRO_LOG_LENGTH; i++)
		macro_history[client][type][i] = false;
}

void lilac_macro_check(int client, const int buttons, int last_buttons)
{
	static int index[MAXPLAYERS + 1];

	if (++index[client] >= tick_rate)
		index[client] = 0;

	for (int i = 0; i < MACRO_ARRAY; i++) {
		/* Skip macros we aren't checking for. */
		if (!process_macro_type(i))
			continue;

		int input = get_macro_input(i);

		/* Player pressed the key. */
		bool key = (!(last_buttons & input) && (buttons & input)) ? true : false;
		macro_history[client][i][index[client]] = key;

		/* Only check for spam when the key is pressed. */
		if (!key)
			continue;

		int acc = 0;

		for (int k = 0; k < tick_rate; k++) {
			if (macro_history[client][i][k])
				acc++;
		}

		if (acc >= macro_max)
			lilac_detected_macro(client, i);
	}
}

static bool process_macro_type(int macro_type)
{
	/* Invalid macro type. */
	if (macro_type >= MACRO_ARRAY || macro_type < 0)
		return false;

	/* 0 == Test for all types. */
	if (!icvar[CVAR_MACRO_MODE])
		return true;

	/* Check bit. */
	return (icvar[CVAR_MACRO_MODE] & (1 << macro_type)) ? true : false;
}

static int get_macro_input(int macro_type)
{
	switch (macro_type) {
	case MACRO_AUTOJUMP: return IN_JUMP;
	case MACRO_AUTOSHOOT: return IN_ATTACK;
	default: return 0;
	}
}

static void lilac_detected_macro(int client, int type)
{
	char string[20];

	/* Clear history, prevents overlap. */
	lilac_macro_reset_client_history(client, type);

	/* Already been logged once, ignore. */
	if (playerinfo_banned_flags[client][CHEAT_MACRO])
		return;

	/* Spam prevention. */
	if (playerinfo_time_forward[client][CHEAT_MACRO] > GetGameTime())
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_MACRO) == false) {
		playerinfo_time_forward[client][CHEAT_MACRO] = GetGameTime() + 5.0;
		return;
	}

	switch (type) {
	case MACRO_AUTOJUMP: { strcopy(string, sizeof(string), "Auto-Jump"); }
	case MACRO_AUTOSHOOT: { strcopy(string, sizeof(string), "Auto-Shoot"); }
	default: { return; } /* Invalid type. */
	}

	lilac_forward_client_cheat(client, CHEAT_MACRO);

	/* Ignore the first detection. */
	if (++macro_detected[client][type] < 2)
		return;

	/* Log (2 == detect, but no logging). */
	if (icvar[CVAR_LOG] && icvar[CVAR_MACRO] < 2) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer),
			"%s was detected of using Macro %s (Detection: %d | Max presses: %d).",
			line_buffer, string, macro_detected[client][type], macro_max);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}

	int char_index;
	while (string[char_index]) {
		string[char_index] = CharToLower(string[char_index]); /* lower each character */
		char_index++;
	}
	Format(string, sizeof(string), "macro_%s", string);
	database_log(client, string, macro_detected[client][type], float(macro_max));

	/* If we are using log-only, then don't warn, there's no point. */
	if (icvar[CVAR_MACRO] > -1) {
		/* Warnings. */
		switch (icvar[CVAR_MACRO_WARNING]) {
		case 1: {
			PrintCenterText(client, "[Little Anti-Cheat] Warning: Macro usage isn't allowed!");
			PrintToChat(client, "[Little Anti-Cheat] Warning: Macro usage isn't allowed!");
		}
		case 2: {
			for (int i = 1; macro_detected[client][type] == 2 && i <= MaxClients; i++) {
				if (!is_player_valid(i) || IsFakeClient(i))
					continue;

				if (!is_player_admin(i))
					continue;

				PrintToChat(i, "[Little Anti-Cheat] %N was detected of using Macro %s.",
					client, string);
			}
		}
		case 3: {
			/* Warn everyone once... */
			if (macro_detected[client][type] == 2)
				PrintToChatAll("[Little Anti-Cheat] %N was detected of using Macro %s.",
					client, string);
		}
		}
	}

	if (macro_detected[client][type] < 5)
		return;

	playerinfo_banned_flags[client][CHEAT_MACRO] = true;

	if (icvar[CVAR_MACRO] == -1)
		return;

	if (icvar[CVAR_MACRO_DEAL_METHOD] == 0)
		KickClient(client, "[Lilac] %T", "kick_macro", client, string);
	else
		lilac_ban_client(client, CHEAT_MACRO);
}

/* Macro detections decrement every 5 minutes.
 * Todo: Might wanna make it more frequent? */
public Action timer_decrement_macro(Handle timer)
{
	for (int i = 1; i <= MaxClients; i++) {
		for (int k = 0; k < MACRO_ARRAY; k++) {
			if (macro_detected[i][k] > 0)
				macro_detected[i][k]--;
		}
	}

	return Plugin_Continue;
}


================================================
FILE: scripting/lilac/lilac_noisemaker.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/* Delet dis! */
#if defined TF2C
	#endinput
#endif

static int noisemaker_type[MAXPLAYERS + 1];
static int noisemaker_entity[MAXPLAYERS + 1];
static int noisemaker_entity_prev[MAXPLAYERS + 1];
static int noisemaker_detection[MAXPLAYERS + 1];

void lilac_noisemaker_reset_client(int client)
{
	noisemaker_type[client] = 0;
	noisemaker_entity[client] = 0;
	noisemaker_entity_prev[client] = 0;
	noisemaker_detection[client] = 0;
}

public Action event_inventoryupdate(Event event, const char[] name, bool dontBroadcast)
{
	int client;

	client = GetClientOfUserId(GetEventInt(event, "userid", 0));
	check_inventory_for_noisemaker(client);

	return Plugin_Continue;
}

void check_inventory_for_noisemaker(int client)
{
	char classname[32];
	int type;

	if (!is_player_valid(client))
		return;

	noisemaker_type[client] = NOISEMAKER_TYPE_NONE;
	noisemaker_entity_prev[client] = noisemaker_entity[client];
	noisemaker_entity[client] = 0;

	for (int i = MaxClients + 1; i < GetEntityCount(); i++) {
		if (!IsValidEdict(i))
			continue;

		if (GetEntPropEnt(i, Prop_Data, "m_hOwnerEntity") != client)
			continue;

		GetEntityClassname(i, classname, sizeof(classname));

		if (!StrEqual(classname, "tf_wearable", false))
			continue;

		type = get_entity_noisemaker_type(GetEntProp(i, Prop_Send, "m_iItemDefinitionIndex"));

		if (type) {
			noisemaker_type[client] = type;
			noisemaker_entity[client] = i;
			return;
		}
	}
}

static int get_entity_noisemaker_type(int itemindex)
{
	switch (itemindex) {
	case 280: return NOISEMAKER_TYPE_LIMITED; /* Black cat. */
	case 281: return NOISEMAKER_TYPE_LIMITED; /* Gremlin. */
	case 282: return NOISEMAKER_TYPE_LIMITED; /* Werewolf. */
	case 283: return NOISEMAKER_TYPE_LIMITED; /* Witch. */
	case 284: return NOISEMAKER_TYPE_LIMITED; /* Banshee. */
	case 286: return NOISEMAKER_TYPE_LIMITED; /* Crazy Laugh. */
	case 288: return NOISEMAKER_TYPE_LIMITED; /* Stabby. */
	case 362: return NOISEMAKER_TYPE_LIMITED; /* Bell. */
	case 364: return NOISEMAKER_TYPE_LIMITED; /* Gong. */
	case 365: return NOISEMAKER_TYPE_LIMITED; /* Koto. */
	case 493: return NOISEMAKER_TYPE_LIMITED; /* Fireworks. */
	case 542: return NOISEMAKER_TYPE_LIMITED; /* Vuvuzela. */

	case 536: return NOISEMAKER_TYPE_UNLIMITED; /* Birthday. */
	case 673: return NOISEMAKER_TYPE_UNLIMITED; /* Winter 2011. */
	}

	return NOISEMAKER_TYPE_NONE;
}

public Action OnClientCommandKeyValues(int client, KeyValues kv)
{
	char command[64];
	KvGetSectionName(kv, command, sizeof(command));

	if (ggame != GAME_TF2)
		return Plugin_Continue;

	if (!icvar[CVAR_ENABLE] || !icvar[CVAR_NOISEMAKER_SPAM])
		return Plugin_Continue;

	if (noisemaker_type[client] != NOISEMAKER_TYPE_LIMITED)
		return Plugin_Continue;

	if (noisemaker_entity_prev[client] != noisemaker_entity[client]) {
		noisemaker_entity_prev[client] = noisemaker_entity[client];
		noisemaker_detection[client] = 0;
	}

	if (StrEqual(command, "+use_action_slot_item_server", false)
		|| StrEqual(command, "-use_action_slot_item_server", false)) {

		/* Since this reacts to both + and -,
		 * and the maximum is 25 uses per noisemaker,
		 * detect the double of that + a buffer of 10. */
		if (++noisemaker_detection[client] > 60)
			lilac_detected_noisemaker(client);
	}

	return Plugin_Continue;
}

static void lilac_detected_noisemaker(int client)
{
	if (playerinfo_banned_flags[client][CHEAT_NOISEMAKER_SPAM])
		return;

	if (lilac_forward_allow_cheat_detection(client, CHEAT_NOISEMAKER_SPAM) == false)
		return;

	playerinfo_banned_flags[client][CHEAT_NOISEMAKER_SPAM] = true;

	lilac_forward_client_cheat(client, CHEAT_NOISEMAKER_SPAM);

	if (icvar[CVAR_LOG]) {
		lilac_log_setup_client(client);
		Format(line_buffer, sizeof(line_buffer), "%s is suspected of using unlimited noisemaker cheats.", line_buffer);

		lilac_log(true);

		if (icvar[CVAR_LOG_EXTRA])
			lilac_log_extra(client);
	}
	database_log(client, "noisemaker", DATABASE_LOG_ONLY);

	/* Enable this later if no false positives are reported. */
	/* lilac_ban_client(client, CHEAT_NOISEMAKER_SPAM); */
}


================================================
FILE: scripting/lilac/lilac_ping.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/


static int ping_high[MAXPLAYERS + 1];
static int ping_warn[MAXPLAYERS + 1];

void lilac_ping_reset_client(int client)
{
	ping_high[client] = 0;
	ping_warn[client] = 0;
}

public Action timer_check_ping(Handle timer)
{
	static bool toggle = true;
	char reason[128];
	float ping;

	if (!icvar[CVAR_ENABLE] || icvar[CVAR_MAX_PING] < 100)
		return Plugin_Continue;

	for (int i = 1; i <= MaxClients; i++) {
		if (!is_player_valid(i) || IsFakeClient(i))
			continue;

		/* Player recently joined, don't check ping yet. */
		if (GetClientTime(i) < 120.0)
			continue;

		ping = GetClientAvgLatency(i, NetFlow_Outgoing) * 1000.0;

		if (ping < float(icvar[CVAR_MAX_PING])) {
			if (toggle && ping_high[i] > 0)
				ping_high[i]--;

			if (ping_high[i] < ping_warn[i] - 2 && ping_warn[i] > 0) {

				ping_warn[i] = 0;
				PrintToChat(i, "[Lilac] Your ping appears to be fine again, it is safe to rejoin a team and play.");
			}

			continue;
		}

		if (++ping_high[i] >= icvar[CVAR_MAX_PING_SPEC] / 5
			&& icvar[CVAR_MAX_PING_SPEC] >= 30) {
			ChangeClientTeam(i, 1); /* Move this player to spectators. */

			ping_warn[i] = ping_high[i];

			PrintToChat(i, "[Lilac] WARNING: You will be kicked in %d seconds if your ping stays too high! (%.0f / %d max)",
				100 - (ping_high[i] * 5),
				ping, icvar[CVAR_MAX_PING]);
		}

		/* Player has a higher ping than maximum for 100 seconds. */
		if (ping_high[i] < 20)
			continue;

		if (icvar[CVAR_LOG_MISC]) {
			lilac_log_setup_client(i);
			Format(line_buffer, sizeof(line_buffer),
				"%s was kicked for having too high ping (%.3fms / %dms max).",
				line_buffer, ping, icvar[CVAR_MAX_PING]);

			lilac_log(true);

			if (icvar[CVAR_LOG_EXTRA] == 2)
				lilac_log_extra(i);
		}
		database_log(i, "high_ping", DATABASE_KICK);

		Format(reason, sizeof(reason), "[Lilac] %T", "tban_ping_high", i,
			ping, icvar[CVAR_MAX_PING]);

		/* Ban the client for three minutes to avoid instant reconnects. */
		BanClient(i, 3, BANFLAG_AUTHID, reason, reason, "lilac", 0);
	}

	toggle = !toggle;

	return Plugin_Continue;
}


================================================
FILE: scripting/lilac/lilac_stock.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

void lilac_warn_admins(int client, int cheat, int detections)
{
	char name[MAX_NAME_LENGTH];
	char type[16];
	int admins[MAXPLAYERS + 1];
	int n = 0;
	
	/* We'll assume the player is valid, as this function
	 * should never be called on invalid clients in the first place. */
	/* if (!is_player_valid(client))
		return; */
	
	/* Setup a list of admins. */
	for (int i = 1; i <= MaxClients; i++) {
		if (!is_player_valid(i))
			continue;
		
		if (IsFakeClient(i))
			continue;
		
		if (is_player_admin(i))
			admins[n++] = i;
	}
	
	/* No admins are on. */
	if (!n)
		return;
	
	switch (cheat) {
	case CHEAT_BHOP: { strcopy(type, sizeof(type), "Bhop"); }
	case CHEAT_AIMBOT: { strcopy(type, sizeof(type), "Aimbot"); }
	case CHEAT_AIMLOCK: { strcopy(type, sizeof(type), "Aimlock"); }
	/* Macros have their own warning system. */
	default: { return; }
	}
	
	if (!GetClientName(client, name, sizeof(name)))
		strcopy(name, sizeof(name), "[NAME_ERROR]");
	
	for (int i = 0; i < n; i++)
		PrintToChat(admins[i],
			"[Lilac] %T", "admin_chat_warning_generic",
			admins[i], name, type, detections);
}

/* Useless Todo: I should update this soon... But I won't :P */
bool bullettime_can_shoot(int client)
{
	int weapon;

	if (!IsPlayerAlive(client))
		return false;

	weapon = GetEntPropEnt(client, Prop_Data, "m_hActiveWeapon");

	if (!IsValidEntity(weapon))
		return false;

	if (GetEntPropFloat(client, Prop_Data, "m_flSimulationTime") + GetTickInterval()
		>= GetEntPropFloat(weapon, Prop_Data, "m_flNextPrimaryAttack"))
		return true;

	return false;
}

void lilac_reset_client(int client)
{
	lilac_backtrack_reset_client(client);
	lilac_bhop_reset_client(client);
	lilac_macro_reset_client(client);
#if !defined TF2C
	/* Noise maker file is empty if compiled for TF2Classic. */
	lilac_noisemaker_reset_client(client);
#endif
	lilac_aimbot_reset_client(client);
	lilac_ping_reset_client(client);
	lilac_convar_reset_client(client);
	lilac_lerp_reset_client(client);

	playerinfo_index[client] = 0;
	playerinfo_aimlock_sus[client] = 0;
	playerinfo_aimlock[client] = 0;
	playerinfo_time_bumpercart[client] = 0.0;
	playerinfo_time_teleported[client] = 0.0;
	playerinfo_time_aimlock[client] = 0.0;
	playerinfo_time_process_aimlock[client] = 0.0;

	for (int i = 0; i < CHEAT_MAX; i++) {
		playerinfo_time_forward[client][i] = 0.0;
		playerinfo_banned_flags[client][i] = false;
	}

	for (int i = 0; i < CMD_LENGTH; i++) {
		playerinfo_buttons[client][i] = 0;
		playerinfo_actions[client][i] = 0;
		playerinfo_time_usercmd[client][i] = 0.0;

		set_player_log_angles(client, view_as<float>({0.0, 0.0, 0.0}), i);
	}
}

void lilac_log_setup_client(int client)
{
	char date[512], steamid[64], ip[64];

	FormatTime(date, sizeof(date), dateformat, GetTime());

	GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid), true);
	GetClientIP(client, ip, sizeof(ip), true);

	FormatEx(line_buffer, sizeof(line_buffer),
		"%s [Version %s] {Name: \"%N\" | SteamID: %s | IP: %s}",
		date, PLUGIN_VERSION, client, steamid, ip);
}

void lilac_log_extra(int client)
{
	char map[128], weapon[64];
	float pos[3], ang[3];

	GetClientAbsOrigin(client, pos);
	GetCurrentMap(map, sizeof(map));
	GetClientWeapon(client, weapon, sizeof(weapon));

	get_player_log_angles(client, 0, true, ang);

	FormatEx(line_buffer, sizeof(line_buffer),
		"\tPos={%.0f,%.0f,%.0f}, Angles={%.5f,%.5f,%.5f}, Map=\"%s\", Team={%d}, Weapon=\"%s\", Latency={Inc:%f,Out:%f}, Loss={Inc:%f,Out:%f}, Choke={Inc:%f,Out:%f}, ConnectionTime={%f seconds}, GameTime={%f seconds}",
		pos[0], pos[1], pos[2],
		ang[0], ang[1], ang[2],
		map, GetClientTeam(client), weapon,
		GetClientAvgLatency(client, NetFlow_Incoming),
		GetClientAvgLatency(client, NetFlow_Outgoing),
		GetClientAvgLoss(client, NetFlow_Incoming),
		GetClientAvgLoss(client, NetFlow_Outgoing),
		GetClientAvgChoke(client, NetFlow_Incoming),
		GetClientAvgChoke(client, NetFlow_Outgoing),
		GetClientTime(client), GetGameTime());

	lilac_log(false);
}

void lilac_log(bool cleanup)
{
	Handle file = OpenFile(log_file, "a");

	if (file == null) {
		PrintToServer("[Lilac] Cannot open log file.");
		return;
	}

	/* Remove invalid characters.
	 * This doesn't care about invalid utf-8 formatting,
	 * only ASCII control characters. */
	if (cleanup) {
		for (int i = 0; line_buffer[i]; i++) {
			if (line_buffer[i] == '\n' || line_buffer[i] == 0x0d)
				line_buffer[i] = '*';
			else if (line_buffer[i] < 32)
				line_buffer[i] = '#';
		}
	}

	WriteFileLine(file, "%s", line_buffer);
	/* Just echo log lines to SourceIRC */
	if (icvar[CVAR_SOURCEIRC] && NATIVE_EXISTS("IRC_MsgFlaggedChannels")) {
		/* Note- SourceIRC Expects messages to be clean with no \r or \n, so clean it if not already done. */
		if (!cleanup) {
			for (int i = 0; line_buffer[i]; i++) {
				if (line_buffer[i] == '\n' || line_buffer[i] == 0x0d)
					line_buffer[i] = '*';
				else if (line_buffer[i] < 32)
					line_buffer[i] = '#';
			}
		}
		IRC_MsgFlaggedChannels("lilac", "[LILAC] %s", line_buffer);
	}
	CloseHandle(file);
}

void lilac_log_first_time_setup()
{
	/* Some admins may not understand how to interpret cheat logs
	 * correctly, thus, we should warn them so they don't panic
	 * over trivial stuff. */
	if (!FileExists(log_file, false, NULL_STRING)) {
		FormatEx(line_buffer, sizeof(line_buffer),
"=========[Notice]=========\n\
Thank you for installing Little Anti-Cheat %s!\n\
Just a few notes about this Anti-Cheat:\n\n\
If a player is logged as \"suspected\" of using cheats, they are not necessarily cheating.\n\
If the suspicions logged are few and rare, they are likely false positives.\n\
An automatic ban is triggered by 5 or more \"suspicions\" or by one \"detection\".\n\
If you think a ban may be incorrect, please do not hesitate to let me know.\n\n\
That is all, have a wonderful day~\n\n\n", PLUGIN_VERSION);
		lilac_log(false);
	}
}

void lilac_ban_client(int client, int cheat)
{
	char reason[128];
	int lang = LANG_SERVER;
	bool log_only = false;

	/* Banning has been disabled, don't forward the ban and don't ban. */
	if (!icvar[CVAR_BAN])
		return;

	/* Check if log only mode has been enabled, in which case, don't ban. */
	switch (cheat) {
	case CHEAT_ANGLES: { log_only = icvar[CVAR_ANGLES] < 0; }
	case CHEAT_CHATCLEAR: { log_only = icvar[CVAR_CHAT] < 0; }
	case CHEAT_CONVAR: { log_only = icvar[CVAR_CONVAR] < 0; }
	case CHEAT_NOLERP: { log_only = icvar[CVAR_NOLERP] < 0; }
	case CHEAT_BHOP: { log_only = icvar[CVAR_BHOP] < 0; }
	/* Aimbot and Aimlock have their own dedicated log-only mode. */
	case CHEAT_ANTI_DUCK_DELAY: { log_only = icvar[CVAR_ANTI_DUCK_DELAY] < 0; }
	case CHEAT_NOISEMAKER_SPAM: { log_only = icvar[CVAR_NOISEMAKER_SPAM] < 0; }
	case CHEAT_MACRO: { log_only = icvar[CVAR_MACRO] < 0; }
	case CHEAT_NEWLINE_NAME: { log_only = icvar[CVAR_FILTER_NAME] < 0; }
	}

	if (log_only)
		return;

	if (icvar[CVAR_BAN_LANGUAGE])
		lang = client;

	switch (cheat) {
	case CHEAT_ANGLES: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_angle", lang); }
	case CHEAT_CHATCLEAR: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_chat_clear", lang); }
	case CHEAT_CONVAR: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_convar", lang); }
	case CHEAT_NOLERP: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_nolerp", lang); }
	case CHEAT_BHOP: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_bhop", lang); }
	case CHEAT_AIMBOT: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_aimbot", lang); }
	case CHEAT_AIMLOCK: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_aimlock", lang); }
	case CHEAT_ANTI_DUCK_DELAY: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_anti_duck_delay", lang); }
	case CHEAT_NOISEMAKER_SPAM: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_noisemaker", lang); }
	case CHEAT_MACRO: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_macro", lang); }
	case CHEAT_NEWLINE_NAME: { Format(reason, sizeof(reason),
		"[Little Anti-Cheat %s] %T", PLUGIN_VERSION, "ban_name_newline", lang); }
	default: return;
	}

	lilac_forward_client_ban(client, cheat);


	/* Try to ban with MateralAdmin first,
	 * if that fails, proceed to SourceBans, then SourceBans++,
	 * And lastly, BaseBans. */


	if (icvar[CVAR_MA] && NATIVE_EXISTS("MABanPlayer")) {
		MABanPlayer(0, client, MA_BAN_STEAM, get_ban_length(cheat), reason);
		CreateTimer(5.0, timer_kick, GetClientUserId(client));
		return;
	}


	if (icvar[CVAR_SB]) {
		if (NATIVE_EXISTS("SBPP_BanPlayer")) {
			SBPP_BanPlayer(0, client, get_ban_length(cheat), reason);
			CreateTimer(5.0, timer_kick, GetClientUserId(client));
			return;
		}
		else if (NATIVE_EXISTS("SBBanPlayer")) {
			SBBanPlayer(0, client, get_ban_length(cheat), reason);
			CreateTimer(5.0, timer_kick, GetClientUserId(client));
			return;
		}
	}


	BanClient(client, get_ban_length(cheat), BANFLAG_AUTO, reason, reason, "lilac", 0);
	CreateTimer(5.0, timer_kick, GetClientUserId(client));
}

public Action timer_kick(Handle timer, int userid)
{
	int client = GetClientOfUserId(userid);

	if (is_player_valid(client))
		KickClient(client, "%T", "kick_ban_generic", client);
		
	return Plugin_Continue;
}

int get_ban_length(int cheat)
{
	return ((ban_length_overwrite[cheat] <= -1) ? icvar[CVAR_BAN_LENGTH] : ban_length_overwrite[cheat]);
}

void get_player_log_angles(int client, int tick, bool latest, float writeto[3])
{
	int i = tick;

	if (latest) {
		i = playerinfo_index[client];
	}
	else {
		while (i < 0)
			i += CMD_LENGTH;
		while (i >= CMD_LENGTH)
			i -= CMD_LENGTH;
	}

	writeto[0] = playerinfo_angles[client][i][0];
	writeto[1] = playerinfo_angles[client][i][1];
	writeto[2] = playerinfo_angles[client][i][2];
}

void set_player_log_angles(int client, float ang[3], int tick)
{
	int i = tick;

	/* Normalize tick. */
	while (i < 0)
		i += CMD_LENGTH;
	while (i >= CMD_LENGTH)
		i -= CMD_LENGTH;

	playerinfo_angles[client][i][0] = ang[0];
	playerinfo_angles[client][i][1] = ang[1];
	playerinfo_angles[client][i][2] = ang[2];
}

void aim_at_point(const float p1[3], const float p2[3], float writeto[3])
{
	SubtractVectors(p2, p1, writeto);
	GetVectorAngles(writeto, writeto);

	while (writeto[0] > 90.0)
		writeto[0] -= 360.0;
	while (writeto[0] < -90.0)
		writeto[0] += 360.0;
	while (writeto[1] > 180.0)
		writeto[1] -= 360.0;
	while (writeto[1] < -180.0)
		writeto[1] += 360.0;

	writeto[2] = 0.0;
}

float angle_delta(float []a1, float []a2)
{
	int normal = 5;
	float p1[3], p2[3], delta;

	p1[0] = a1[0];
	p2[0] = a2[0];
	p2[1] = a2[1];
	p1[1] = a1[1];

	/* We don't care about roll. */
	p1[2] = 0.0;
	p2[2] = 0.0;

	delta = GetVectorDistance(p1, p2);

	/* Normalize maximum 5 times, yaw can sometimes be odd. */
	while (delta > 180.0 && normal > 0) {
		normal--;
		delta = FloatAbs(delta - 360.0);
	}

	return delta;
}

bool skip_due_to_loss(int client)
{
	/* Debate: What percentage should this be at?
	 * Skip detection if the loss is more than 50% */
	if (icvar[CVAR_LOSS_FIX])
		return GetClientAvgLoss(client, NetFlow_Both) > 0.5;

	return false;
}

int time_to_ticks(float time)
{
	if (time > 0.0)
		return RoundToNearest(time / GetTickInterval());

	return 0;
}

int intabs(int num)
{
	return ((num < 0) ? num * -1 : num);
}

bool is_player_admin(int client)
{
	/* Todo: I don't know if this is correct. */
	return CheckCommandAccess(client, "", ADMFLAG_GENERIC | ADMFLAG_KICK | ADMFLAG_SLAY, true);
}

bool is_player_valid(int client)
{
	return (client >= 1 && client <= MaxClients
		&& IsClientConnected(client) && IsClientInGame(client)
		&& !IsClientSourceTV(client));
}

void lilac_forward_client_cheat(int client, int cheat)
{
	int dummy;

	if (forwardhandle == null)
		return;

	Call_StartForward(forwardhandle);
	Call_PushCell(client);
	Call_PushCell(cheat);
	Call_Finish(dummy);
}

void lilac_forward_client_ban(int client, int cheat)
{
	int dummy;

	if (forwardhandleban == null)
		return;

	Call_StartForward(forwardhandleban);
	Call_PushCell(client);
	Call_PushCell(cheat);
	Call_Finish(dummy);
}

bool lilac_forward_allow_cheat_detection(int client, int cheat)
{
	Action result = Plugin_Continue;

	if (forwardhandleallow == null)
		return true;

	Call_StartForward(forwardhandleallow);
	Call_PushCell(client);
	Call_PushCell(cheat);
	Call_Finish(result);

	if (result == Plugin_Continue)
		return true;

	return false;
}


================================================
FILE: scripting/lilac/lilac_string.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs)
{
	int flags;

	if (!icvar[CVAR_ENABLE])
		return Plugin_Continue;

	/* Prevent players banned for Chat-Clear from spamming chat.
	 * Helps legit players see the cheater was banned. */
	if (playerinfo_banned_flags[client][CHEAT_CHATCLEAR])
		return Plugin_Stop;

	if (!icvar[CVAR_FILTER_CHAT])
		return Plugin_Continue;

	/* Just for future reference, because it may be unclear
	 * how "is_string_valid()" works.
	 * Normally, it will set the "flags" variable with bits
	 * telling you what's wrong with a string and return false.
	 * But the wide-char-spam bit is an exception,
	 * it will set the bit, but won't return false.
	 * This is because wide-char spam gets its own message
	 * when blocking the chat.
	 * Plus, it's still technically a valid string. */

	/* Invalid string and no newlines/carriage returns.
	 * Newlines in chat will we dealt with in post.
	 * This is just so people can see that the player did indeed
	 * clear the chat before banning, otherwise people
	 * would be confused and think the ban was an error. */
	if (!(is_string_valid(sArgs, flags)) && !(flags & STRFLAG_NEWLINE)) {
		PrintToChat(client, "[Lilac] %T", "chat_invalid_characters", client);
		return Plugin_Stop;
	}
	else if ((flags & STRFLAG_WIDE_CHAR_SPAM)) {
		/* Wide char spam (Example: Bismillah spam),
		 * as explained by 3kliksphilip here: https://youtu.be/hP1N1YRitlM?t=94
		 * this clears the chat and is annoying.
		 * Block this exploit. */
		PrintToChat(client, "[Lilac] %T", "chat_wide_char_spam", client);
		return Plugin_Stop;
	}

	return Plugin_Continue;
}

public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs)
{
	/* Chat-Clear doesn't work in CS:GO. */
	if (ggame == GAME_CSGO)
		return;

	/* Todo: CVAR_CHAT is... Now an outdated name... */
	if (!icvar[CVAR_ENABLE] || !icvar[CVAR_CHAT])
		return;

	/* Don't log chat-clear more than once. */
	if (playerinfo_banned_flags[client][CHEAT_CHATCLEAR])
		return;

	if (does_string_contain_newline(sArgs)) {
		if (lilac_forward_allow_cheat_detection(client, CHEAT_CHATCLEAR) == false)
			return;

		playerinfo_banned_flags[client][CHEAT_CHATCLEAR] = true;
		lilac_forward_client_cheat(client, CHEAT_CHATCLEAR);

		if (icvar[CVAR_LOG]) {
			lilac_log_setup_client(client);
			Format(line_buffer, sizeof(line_buffer),
				"%s was detected and banned for Chat-Clear (Chat message: %s)",
				line_buffer, sArgs);

			lilac_log(true);

			if (icvar[CVAR_LOG_EXTRA])
				lilac_log_extra(client);
		}
		database_log(client, "chat_clear", DATABASE_BAN);

		lilac_ban_client(client, CHEAT_CHATCLEAR);
	}
}

/* Quick and fast, better to use this than validate the entire string twice. */
static bool does_string_contain_newline(const char []string)
{
	for (int i = 0; string[i]; i++) {
		/* Newline or carriage return. */
		if (string[i] == '\n' || string[i] == '\r')
			return true;
	}

	return false;
}

public Action event_namechange(Event event, const char[] name, bool dontBroadcast)
{
	int client;
	char client_name[MAX_NAME_LENGTH];

	client = GetClientOfUserId(GetEventInt(event, "userid", 0));

	if (skip_name_check(client))
		return Plugin_Continue;

	GetEventString(event, "newname", client_name, sizeof(client_name), "");
	check_name(client, client_name);

	return Plugin_Continue;
}

void lilac_string_check_name(int client)
{
	char name[MAX_NAME_LENGTH];

	if (skip_name_check(client))
		return;

	if (!GetClientName(client, name, sizeof(name)))
		return;

	check_name(client, name);
}

static bool skip_name_check(int client)
{
	if (!icvar[CVAR_ENABLE]
		|| !icvar[CVAR_FILTER_NAME]
		|| !is_player_valid(client)
		|| IsFakeClient(client))
		return true;

	return false;
}

static void check_name(int client, const char []name)
{
	int flags;

	if (is_string_valid(name, flags))
		return;

	/* Todo: Currently logging "const char []name" because on the
	 * event name_change, we don't actually log the player's
	 * most recent name, rather their previous name.
	 * I should fix that, but for now, lets do this instead (tmp fix). */

	/* Player was detected of having a newline or carriage return in their name, which is a cheat feature... */
	if (icvar[CVAR_FILTER_NAME] == 2 && (flags & STRFLAG_NEWLINE)) {
		if (playerinfo_banned_flags[client][CHEAT_NEWLINE_NAME])
			return;

		if (lilac_forward_allow_cheat_detection(client, CHEAT_NEWLINE_NAME) == false)
			return;

		playerinfo_banned_flags[client][CHEAT_NEWLINE_NAME] = true;
		lilac_forward_client_cheat(client, CHEAT_NEWLINE_NAME);

		if (icvar[CVAR_LOG]) {
			lilac_log_setup_client(client);
			Format(line_buffer, sizeof(line_buffer),
				"%s was banned of having newline characters in their name (%s).", line_buffer, name);

			lilac_log(true);

			if (icvar[CVAR_LOG_EXTRA])
				lilac_log_extra(client);
		}
		database_log(client, "name_newline", DATABASE_BAN);

		lilac_ban_client(client, CHEAT_NEWLINE_NAME);
	}
	else {
		/* Invalid name. */
		if (icvar[CVAR_LOG_MISC]) {
			lilac_log_setup_client(client);
			Format(line_buffer, sizeof(line_buffer),
				"%s was kicked for having invalid characters in their name (%s).", line_buffer, name);

			lilac_log(true);

			if (icvar[CVAR_LOG_EXTRA])
				lilac_log_extra(client);
		}
		database_log(client, "name_invalid", DATABASE_KICK);

		/* Log only. */
		if (icvar[CVAR_FILTER_NAME] > 0)
			KickClient(client, "[Lilac] %T", "kick_bad_name", client);
	}
}



/*
	UTF-8 validater and Blacklist...
	My old UTF-8 checker was soooo ugly, I had to change it.

	Note: 5 and 6 byte encodings aren't valid in UTF-8 anymore.
	Originally, UTF-8 could have up to 6 byte long encodings, but in
	2003 it was changed to be maximum 4 bytes, to match the limitations
	of UTF-16.

	2003 was long ago, and so the 4 byte maximum is used here.
*/


static bool is_string_valid(const char []string, int &flags)
{
	int widechars = 0;
	int i = 0;
	flags = 0;

	while (string[i]) {
		int codepoint = 0;
		int len = utf8_decode(string[i], codepoint); /* SPawn pointer logic sucks :( */

		/* Invalid UTF-8 encoding. */
		if (len == 0)
			return false;

		switch (codepoint) {
		case '\n', '\r': {
			flags = STRFLAG_NEWLINE;
			return false;
		}
		case 0xfdfd /* Bismillah. */ : {
			if (++widechars > 3)
				flags |= STRFLAG_WIDE_CHAR_SPAM;
		}
		}

		/* Other than the UTF-16 ranges and unicode limit,
		 * these are just blacklisted codepoints and are valid UTF-8.
		 * Private Use Areas and Control Characters are not allowed. */

		/* UTF-16 reserved surgate halves, not valid codepoint. */
		if (codepoint >= 0xd800 && codepoint <= 0xdfff)
			return false;
		else if (codepoint > 0x10ffff) /* Unicode limit. */
			return false;
		else if (codepoint < 0x20 && codepoint != '\t') /* C0 control chars.*/
			return false;
		else if (codepoint == 0x7f) /* Not really C0, but will count as one. */
			return false;
		else if (codepoint >= 0x80 && codepoint <= 0x9f) /* C1 control chars. */
			return false;
		else if (codepoint >= 0xe000 && codepoint <= 0xf8ff) /* PUA. */
			return false;
		else if (codepoint >= 0xf0000 && codepoint <= 0xfffff) /* PUA. */
			return false;
		else if (codepoint >= 0x100000 && codepoint <= 0x10fffd) /* PUA. */
			return false;

		i += len;
	}

	/* No invalid encodings or codepoints found :) */
	return true;
}

/* This function does not check for valid codepoints.
 * However, this will check for overlong encodings.
 * Returns the length of the encoding, 0 on error. */
static int utf8_decode(const char []ptr, int &codepoint)
{
	static const int mask[] = {0, 0, 0x1f, 0x0f, 0x07};

	int len = utf8_header_length(ptr[0]);
	if (len == 0) {
		return 0;
	}
	else if (len == 1) {
		codepoint = ptr[0];
		return 1;
	}

	codepoint = ptr[0] & mask[len];
	for (int i = 1; i < len; i++) {
		if ((ptr[i] & 0xc0) != 0x80)
			return 0;

		codepoint = (codepoint << 6) | (ptr[i] & 0x3f);
	}

	if (len != codepoint_to_utf8_length(codepoint))
		return 0;

	return len;
}

static int utf8_header_length(char c)
{
	/* Codepoint will always be above the U+10ffff limit. */
	if (c >= 0xf5)
		return 0;
	/* Can only be invalid codepoints; two byte ASCII. */
	else if (c == 0xc0 || c == 0xc1)
		return 0;

	switch ((c & 0xf0)) {
	/* Masking can include an extra bit for two byte encodings. */
	case 0xc0, 0xd0: return 2;
	case 0xe0: return 3;
	case 0xf0: return 4;
	default: return 1;
	}
}

static int codepoint_to_utf8_length(int codepoint)
{
	if (codepoint < 0x80)
		return 1;
	else if (codepoint < 0x800)
		return 2;
	else if (codepoint < 0x10000)
		return 3;
	else if (codepoint < 0x110000) /* U+10ffff + 1 */
		return 4;
	else
		return 0;
}


================================================
FILE: scripting/lilac.sp
================================================
/*
	Little Anti-Cheat
	Copyright (C) 2018-2023 J_Tanzanite

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/* Uncomment below line to compile for Team Fortress 2 Classic.
 * You'll need SourceMod 1.10 and SM-TF2Clasic-Tools to compile, if you decide to.
 * Note: Doing so means the compiled plugin won't work correctly
 * for other source games. 
 * SourceMod 1.10: https://www.sourcemod.net/downloads.php?branch=1.10-dev
 * SM-TF2Clasic-Tools: https://github.com/tf2classic/SM-TF2Classic-Tools */
//#define TF2C

#include <sourcemod>
#include <sdktools_engine>
#include <sdktools_entoutput>
#include <convar_class>
#undef REQUIRE_PLUGIN /* ... */
#undef REQUIRE_EXTENSIONS
#if defined TF2C
	#include <tf2c>
#else
	#include <tf2>
	#include <tf2_stocks>
#endif
#define REQUIRE_PLUGIN
#define REQUIRE_EXTENSIONS

#pragma semicolon 1
#pragma newdecls required

#include "lilac/lilac_globals.sp" /* Must be at top, contains defines. */

#include "lilac/lilac_aimbot.sp"
#include "lilac/lilac_aimlock.sp"
#include "lilac/lilac_angles.sp"
#include "lilac/lilac_anti_duck_delay.sp"
#include "lilac/lilac_backtrack.sp"
#include "lilac/lilac_bhop.sp"
#include "lilac/lilac_config.sp"
#include "lilac/lilac_convar.sp"
#include "lilac/lilac_database.sp"
#include "lilac/lilac_lerp.sp"
#include "lilac/lilac_macro.sp"
#include "lilac/lilac_noisemaker.sp"
#include "lilac/lilac_ping.sp"
#include "lilac/lilac_stock.sp"
#include "lilac/lilac_string.sp" /* String takes care of chat and names. */


public Plugin myinfo = {
	name = PLUGIN_NAME,
	author = PLUGIN_AUTHOR,
	description = PLUGIN_DESC,
	version = PLUGIN_VERSION,
	url = PLUGIN_URL
};


public void OnPluginStart()
{
	LoadTranslations("lilac.phrases.txt");

#if defined TF2C
	ggame = GAME_TF2;
	/* Post inventory check isn't needed,
	 * as noisemaker spam isn't a thing in TF2Classic. */
	HookEvent("player_teleported", event_teleported, EventHookMode_Post);
	HookEvent("player_death", event_player_death_tf2, EventHookMode_Pre);
#else
	char gamefolder[32];
	GetGameFolderName(gamefolder, sizeof(gamefolder));
	if (StrEqual(gamefolder, "tf", false)) {
		ggame = GAME_TF2;

		HookEvent("post_inventory_application", event_inventoryupdate, EventHookMode_Post);
		HookEvent("player_teleported", event_teleported, EventHookMode_Post);
	}
	else if (StrEqual(gamefolder, "cstrike", false)) {
		ggame = GAME_CSS;
	}
	else if (StrEqual(gamefolder, "csgo", false)) {
		ConVar tvar;
		ggame = GAME_CSGO;

		if ((tvar = FindConVar("sv_autobunnyhopping")) != null) {
			force_disable_bhop = tvar.IntValue;
			tvar.AddChangeHook(cvar_change);
		}
		else {
			/* We weren't able to get the cvar,
			 * disable bhop checks just in case. */
			force_disable_bhop = 1;

			PrintToServer("[Lilac] Unable to to find convar \"sv_autobunnyhopping\", bhop checks have been forcefully disabled.");
		}
	}
	else if (StrEqual(gamefolder, "left4dead2", false)) {
		ggame = GAME_L4D2;
	}
	else if (StrEqual(gamefolder, "left4dead", false)) {
		ggame = GAME_L4D;
	}
	else if (StrEqual(gamefolder, "dod", false)) {
		ggame = GAME_DODS;
	}
	else {
		ggame = GAME_UNKNOWN;
		PrintToServer("[Lilac] This game currently isn't supported, Little Anti-Cheat will still run, but expect some bugs and false positives/bans!");
	}

	if (ggame == GAME_TF2)
		HookEvent("player_death", event_player_death_tf2, EventHookMode_Pre);
	else
		HookEvent("player_death", event_player_death, EventHookMode_Pre);
#endif /* TF2C check. */

	HookEvent("player_spawn", event_teleported, EventHookMode_Post);
	HookEvent("player_changename", event_namechange, EventHookMode_Post);

	HookEntityOutput("trigger_teleport", "OnEndTouch", map_teleport);

	/* Default ban lengths are -1. (Global ConVar). */
	for (int i = 0; i < CHEAT_MAX; i++)
		ban_length_overwrite[i] = -1;

	/* Bans for Bhop last 1 month by default. */
	ban_length_overwrite[CHEAT_BHOP] = 24 * 30 * 60;

	/* Bans for Macros are 15 minutes by default. */
	ban_length_overwrite[CHEAT_MACRO] = 15;

	/* If sv_maxupdaterate is changed mid-game and then this plugin
	 * is loaded, then it could lead to false positives.
	 * Reset all stats on all players already in-game, but ignore lerp.
	 * Also check players already in-game for noisemaker. */
	for (int i = 1; i <= MaxClients; i++) {
		lilac_reset_client(i);
		lilac_lerp_ignore_nolerp_client(i);
#if !defined TF2C
		check_inventory_for_noisemaker(i);
#endif
	}

	forwardhandle = CreateGlobalForward("lilac_cheater_detected",
		ET_Ignore, Param_Cell, Param_Cell);
	forwardhandleban = CreateGlobalForward("lilac_cheater_banned",
		ET_Ignore, Param_Cell, Param_Cell);
	forwardhandleallow = CreateGlobalForward("lilac_allow_cheat_detection",
		ET_Event, Param_Cell, Param_Cell);

	CreateTimer(QUERY_TIMER, timer_query, _, TIMER_REPEAT);
	CreateTimer(5.0, timer_check_ping, _, TIMER_REPEAT);
	CreateTimer(5.0, timer_check_lerp, _, TIMER_REPEAT);
	CreateTimer(0.5, timer_check_aimlock, _, TIMER_REPEAT);
	CreateTimer(60.0 * 5.0, timer_decrement_macro, _, TIMER_REPEAT);

	tick_rate = RoundToNearest(1.0 / GetTickInterval());

	/* Ignore low tickrates. */
	macro_max = (tick_rate >= 60 && tick_rate <= MACRO_LOG_LENGTH) ? 20 : 0;

	if (tick_rate > 50) {
		bhop_settings_min[BHOP_INDEX_MIN] = 5;
		bhop_settings_min[BHOP_INDEX_MAX] = 10;
		bhop_settings_min[BHOP_INDEX_TOTAL] = 1;
	}
	else {
		bhop_settings_min[BHOP_INDEX_MIN] = 10;
		bhop_settings_min[BHOP_INDEX_MAX] = 20;
		bhop_settings_min[BHOP_INDEX_TOTAL] = 3;
	}
	bhop_settings_min[BHOP_INDEX_JUMP] = -1;
	bhop_settings_min[BHOP_INDEX_AIR] = 0;

	/* This sets up convars and such. */
	lilac_config_setup();

	if (icvar[CVAR_LOG])
		lilac_log_first_time_setup();
}

public void OnAllPluginsLoaded()
{
	sourcebanspp_exist = LibraryExists("sourcebans++");
	sourcebans_exist = LibraryExists("sourcebans");
	materialadmin_exist = LibraryExists("materialadmin");

	if (LibraryExists("updater"))
		lilac_update_url();

	/* Startup message. */
	PrintToServer("[Little Anti-Cheat %s] Successfully loaded!", PLUGIN_VERSION);
}

public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int err_max)
{
	/* Been told this isn't needed, but just in case. */
	MarkNativeAsOptional("SBBanPlayer");
	MarkNativeAsOptional("SBPP_BanPlayer");
	MarkNativeAsOptional("MABanPlayer");
	MarkNativeAsOptional("Updater_AddPlugin");
	MarkNativeAsOptional("Updater_RemovePlugin");
	MarkNativeAsOptional("IRC_MsgFlaggedChannels");

	/* Build the log path for the file in case the user has overridden sm_basepath. */
	BuildPath(Path_SM, log_file, sizeof(log_file), "logs/lilac.log");
	return APLRes_Success;
}

public void OnLibraryAdded(const char []name)
{
	if (StrEqual(name, "sourcebans++"))
		sourcebanspp_exist = true;
	else if (StrEqual(name, "sourcebans"))
		sourcebans_exist = true;
	else if (StrEqual(name, "materialadmin"))
		materialadmin_exist = true;
	else if (StrEqual(name, "updater"))
		lilac_update_url();
}

public void OnLibraryRemoved(const char []name)
{
	if (StrEqual(name, "sourcebans++"))
		sourcebanspp_exist = false;
	else if (StrEqual(name, "sourcebans"))
		sourcebans_exist = false;
	else if (StrEqual(name, "materialadmin"))
		materialadmin_exist = false;
}

void lilac_update_url()
{
	if (icvar[CVAR_AUTO_UPDATE]) {
		if (!NATIVE_EXISTS("Updater_AddPlugin")) {
			PrintToServer("Error: Native Updater_AddPlugin() not found! Check if updater plugin is installed.");
			return;
		}

		Updater_AddPlugin(UPDATE_URL);
	}
	else {
		if (!NATIVE_EXISTS("Updater_RemovePlugin")) {
			PrintToServer("Error: Native Updater_RemovePlugin() not found! Check if updater plugin is installed.");
			return;
		}

		Updater_RemovePlugin();
	}
}

public void OnClientPutInServer(int client)
{
	lilac_reset_client(client);
	lilac_string_check_name(client);

	CreateTimer(30.0, timer_welcome, GetClientUserId(client));
}

public void TF2_OnConditionRemoved(int client, TFCond condition)
{
	if (condition == TFCond_Taunting)
		playerinfo_time_teleported[client] = GetGameTime();
	else if (condition == TFCond_HalloweenKart)
		playerinfo_time_bumpercart[client] = GetGameTime();
}

public Action event_teleported(Event event, const char[] name, bool dontBroadcast)
{
	int client = GetClientOfUserId(GetEventInt(event, "userid", -1));

	if (is_player_valid(client))
		playerinfo_time_teleported[client] = GetGameTime();

	return Plugin_Continue;
}

public void map_teleport(const char[] output, int caller, int activator, float delay)
{
	if (!is_player_valid(activator) || IsFakeClient(activator))
		return;

	playerinfo_time_teleported[activator] = GetGameTime();
}

public Action timer_welcome(Handle timer, int userid)
{
	int client = GetClientOfUserId(userid);

	/* Todo: Considering there are log-only options now...
	 * Perhaps I should check if ANYTHING can ban at all. */
	if (is_player_valid(client) && icvar[CVAR_WELCOME]
		&& icvar[CVAR_ENABLE] && icvar[CVAR_BAN])
		PrintToChat(client, "[Lilac] %T", "welcome_msg", client, PLUGIN_VERSION);

	return Plugin_Continue;
}

public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3],
				float angles[3], int& weapon, int& subtype, int& cmdnum,
				int& tickcount, int& seed, int mouse[2])
{
	static int lbuttons[MAXPLAYERS + 1];

	if (!is_player_valid(client) || IsFakeClient(client))
		return Plugin_Continue;

	/* Increment the index. */
	if (++playerinfo_index[client] >= CMD_LENGTH)
		playerinfo_index[client] = 0;

	/* Store when the tick was processed. */
	playerinfo_time_usercmd[client][playerinfo_index[client]] = GetGameTime();

	/* Store information. */
	lilac_backtrack_store_tickcount(client, tickcount);
	set_player_log_angles(client, angles, playerinfo_index[client]);
	playerinfo_buttons[client][playerinfo_index[client]] = buttons;
	playerinfo_actions[client][playerinfo_index[client]] = 0;

	if ((buttons & IN_ATTACK) && bullettime_can_shoot(client))
		playerinfo_actions[client][playerinfo_index[client]] |= ACTION_SHOT;

	if (icvar[CVAR_ENABLE]) {
#if !defined TF2C
		/* Detect Anti-Duck-Delay. */
		if (ggame == GAME_CSGO && icvar[CVAR_ANTI_DUCK_DELAY])
			lilac_anti_duck_delay_check(client, buttons);
#endif
		/* Detect Angle-Cheats. */
		if (icvar[CVAR_ANGLES])
			lilac_angles_check(client, angles);

		/* Detect Macros. */
		if (macro_max && icvar[CVAR_MACRO])
			lilac_macro_check(client, buttons, lbuttons[client]);

		/* Detect bhop. */
		if (!force_disable_bhop && icvar[CVAR_BHOP])
			lilac_bhop_check(client, buttons, lbuttons[client]);

		/* Patch Angle-Cheats. */
		if (icvar[CVAR_PATCH_ANGLES])
			lilac_angles_patch(angles);

		/* Patch Backtracking. */
		if (icvar[CVAR_BACKTRACK_PATCH])
			tickcount = lilac_backtrack_patch(client, tickcount);
	}

	lbuttons[client] = buttons;

	return Plugin_Continue;
}


================================================
FILE: translations/chi/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"chi"		"本服务器受到LAC反作弊系统保护 {1}"
	}

	"kick_query_failure"
	{
		"chi"		"错误:查询未响应,如果此问题仍然存在,请重新启动游戏"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"chi"		"检测到参数利用:您的interp过高({1}ms / {2}ms 最高)。请设置您的 cl_interp 参数至 {3} 或更低。"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"chi"		"您的Ping值过高! ({1} / {2} 最高)"
	}

	"kick_ban_genetic"
	{
		"chi"		"您已经被此服务器封禁。"
	}

	"ban_angle"
	{
		"chi"		"检测到异常角度"
	}

	"ban_chat_clear"
	{
		"chi"		"检测到清屏作弊"
	}

	"ban_convar"
	{
		"chi"		"检测到非法变量"
	}
	"ban_nolerp"
	{
		"chi"		"检测到非法Lerp值"
	}

	"ban_bhop"
	{
		"chi"		"检测到连跳脚本"
	}

	"ban_aimbot"
	{
		"chi"		"检测到自瞄"
	}

	"ban_aimlock"
	{
		"chi"		"检测到暴力锁头"
	}
	"ban_anti_duck_delay"
	{
		"chi"		"检测到下蹲辅助"
	}

	"ban_noisemaker"
	{
		"chi"		"检测到刷屏"
	}
	"ban_macro"
	{
		"chi"		"检测到辅助宏"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"chi"		"此服务器不允许使用{1}辅助宏 "
	}

	"chat_invalid_characters"
	{
		"chi" 		"你的聊天信息已被屏蔽 (原因: 无效字符)"
	}

	"chat_wide_char_spam"
	{
		"chi"		"你的聊天信息已被屏蔽 (原因: 宽字符垃圾信息)"
	}

	"kick_bad_name"
	{
		"chi"		"你的游戏名称含有非法字符, 请更改你的游戏名称"
	}

	"ban_name_newline"
	{
		"chi"		"检测到名称含有换行符"
	}

	"admin_chat_warning_generic"
	{
		"#format"	"{1:s},{2:s},{3:d}"
		"chi"		"{1} 可能使用了 '{2}' (检测次数: {3})"
	}
}


================================================
FILE: translations/cze/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"cze"		"Tento server je chráněn Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"cze"		"Error: Selhnání odpovědi na dotaz, restartujte hru pokud problém nadále přetrvává"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"cze"		"Detekováno neférové zneužití: Interp je příliš velký. ({1}ms / {2}ms max). Prosím nastavte svůj cl_interp na hodnotu {3} či méně"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"cze"		"Váš ping je příliš vysoký ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"cze"		"Byl jste zabanován z tohoto serveru"
	}

	"ban_angle"
	{
		"cze"		"Angle-Cheats detekován"
	}

	"ban_chat_clear"
	{
		"cze"		"Chat-Clear detekován"
	}

	"ban_convar"
	{
		"cze"		"Nalezen špatný ConVar"
	}

	"ban_bhop"
	{
		"cze"		"Bhop detekován"
	}

	"ban_aimbot"
	{
		"cze"		"Aimbot detekován"
	}

	"ban_aimlock"
	{
		"cze"		"Aimlock detekován"
	}
}


================================================
FILE: translations/da/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"da"		"Denne server er beskyttet af Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"da"		"Fejl: Forespørgelsen fejlede, genstart dit spil hvis dette problem opstår igen"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"da"		"Exploit opdaget: Din interp er for høj ({1}ms / {2}ms max). Venlist sæt din cl_interp tilbage til {3} eller lavere"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"da"		"Din ping er for høj ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"da"		"Du er blevet banned fra denne server"
	}

	"ban_angle"
	{
		"da"		"Vinkel-Cheats Opdaget"
	}

	"ban_chat_clear"
	{
		"da"		"Chat-Clear Opdaget"
	}

	"ban_convar"
	{
		"da"		"Ugyldig ConVar Opdaget"
	}

	"ban_nolerp"
	{
		"da"		"NoLerp Opdaget"
	}

	"ban_bhop"
	{
		"da"		"Bhop Opdaget"
	}

	"ban_aimbot"
	{
		"da"		"Aimbot Opdaget"
	}

	"ban_aimlock"
	{
		"da"		"Aimlock Opdaget"
	}

	"ban_anti_duck_delay"
	{
		"da" 		"Anti-Duck-Forsinkelse Opdaget"
	}

	"ban_noisemaker"
	{
		"da" 		"Uendelig Støj Generator Opdaget"
	}

	"ban_macro"
	{
		"da"		"Macro Opdaget"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"da"		"{1} Macro er ikke tilladt på denne server"
	}

	"chat_invalid_characters"
	{
		"da" 		"Din chat besked er blevet blokeret (Årsag: Ugyldige karakterer)"
	}

	"chat_wide_char_spam"
	{
		"da"		"Din chat besked er blevet blokeret (Årsag: Wide-Char Spam)"
	}

	"kick_bad_name"
	{
		"da"		"Dit navn indeholder ugyldige karakterer, venligst ændre dit navn"
	}

	"ban_name_newline"
	{
		"da"		"Navn Newlines Opdaget"
	}
}


================================================
FILE: translations/de/lilac.phrases.txt
================================================
"Phrases"
{
        "welcome_msg"
        {
                "#format"        "{1:s}"
                "de"             "Dieser Server wird von Little Anti-Cheat geschützt {1}"
        }

        "kick_query_failure"
        {
                "de"             "Fehler: Anfrage konnte nicht bearbeitet werden, bitte starten Sie Ihr Spiel neu, falls dieses Problem weiterhin besteht"
        }

        "kick_interp_exploit"
        {
                "#format"       "{1:.0f},{2:d},{3:.3f}"
                "de"            "Exploit entdeckt: Ihr Interp ist zu hoch ({1}ms / {2}ms max). Bitte stellen Sie Ihre cl_interp zurück auf {3} oder niedriger ein"
        }

        "tban_ping_high"
        {
                "#format"       "{1:.0f},{2:d}"
                "de"            "Ihr Ping ist zu hoch ({1} / {2} max)"
        }

        "kick_ban_generic"
        {
                "de"            "Sie wurden von diesem Server ausgeschlossen"
        }

        "ban_angle"
        {
                "de"            "Angle-Cheats entdeckt"
        }

        "ban_chat_clear"
        {
                "de"            "Chat-Cleaner entdeckt"
        }

        "ban_convar"
        {
                "de"            "Manipulierte ConVar entdeckt"
        }

        "ban_bhop"
        {
                "de"            "BHop Script entdeckt"
        }

        "ban_aimbot"
        {
                "de"            "Aimbot entdeckt"
        }

        "ban_aimlock"
        {
                "de"            "Aimlock entdeckt"
        }
}


================================================
FILE: translations/es/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"es"		"El servidor está protegido por Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"es"		"Error: No hay respuesta en la consulta, reinicia el juego si el problema continúa."
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"es"		"Detectado Exploit: Tu Interp es demasiado alto ({1}ms / {2}ms máx). Por favor, ajusta el comando cl_interp a {3} o por debajo"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"es"		"Tu Ping es demasiado alto ({1} / {2} máx)"
	}

	"kick_ban_generic"
	{
		"es"		"Has sido prohibido en este servidor"
	}

	"ban_angle"
	{
		"es"		"Detectado Angle-Cheats"
	}

	"ban_chat_clear"
	{
		"es"		"Detectado Chat-Cleaner"
	}

	"ban_convar"
	{
		"es"		"Detectado ConVar no permitido"
	}

	"ban_nolerp"
	{
		"es"		"Detectado NoLerp"
	}

	"ban_bhop"
	{
		"es"		"Detectado Bhop"
	}

	"ban_aimbot"
	{
		"es"		"Detectado Aimbot"
	}

	"ban_aimlock"
	{
		"es"		"Detectado Aimlock"
	}

	"ban_anti_duck_delay"
	{
		"es" 		"Detectado Anti-Duck-Delay"
	}

	"ban_noisemaker"
	{
		"es" 		"Detectado Noisemaker infinito"
	}

	"ban_macro"
	{
		"es"		"Detectada Macro"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"es"		"{1} las macros no están permitidas en este servidor"
	}

	"chat_invalid_characters"
	{
		"es" 		"Mensaje bloqueado en el chat, detectados caracteres inválidos"
	}

	"chat_wide_char_spam"
	{
		"es"		"Mensaje bloqueado en el chat, detectado spam de caracteres"
	}

	"kick_bad_name"
	{
		"es"		"Tu nombre contiene caracteres inválidos, por favor cámbiate nombre"
	}

	"ban_name_newline"
	{
		"es"		"Detectado Newlines en el nombre"
	}
}


================================================
FILE: translations/fi/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"fi"		"Tämä palvelin on suojattu Little Anti-Cheatilla {1}"
	}

	"kick_query_failure"
	{
		"fi"		"Error: Query response häiriö. Käynnistä pelisi uudelleen, jos ongelma jatkuu"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"fi"		"Exploit havaittu: sinun interp on liian korkea ({1}ms / {2}ms max). Aseta cl_interp takaisin {3} tai matalemmaksi"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"fi"		"Viiveesi on liian korkea ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"fi"		"Sinut on bannattu tältä palvelimelta"
	}

	"ban_angle"
	{
		"fi"		"Angle-Cheats havaittu"
	}

	"ban_chat_clear"
	{
		"fi"		"Chat-Clear havaittu"
	}

	"ban_convar"
	{
		"fi"		"Invalid ConVar havaittu"
	}

	"ban_nolerp"
	{
		"fi"		"NoLerp havaittu"
	}

	"ban_bhop"
	{
		"fi"		"Bhop havaittu"
	}

	"ban_aimbot"
	{
		"fi"		"Aimbot havaittu"
	}

	"ban_aimlock"
	{
		"fi"		"Aimlock havaittu"
	}

	"ban_anti_duck_delay"
	{
		"fi" 		"Anti-Duck-Delay havaittu"
	}

	"ban_noisemaker"
	{
		"fi" 		"Infinite Noise Maker havaittu"
	}

	"ban_macro"
	{
		"fi"		"Macro havaittu"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"fi"		"{1} Macro ei ole sallittu tällä palvelimella"
	}

	"chat_invalid_characters"
	{
		"fi" 		"Sinun chat-viestisi on estetty (Syy: Tuntemattomia merkkejä)."
	}

	"chat_wide_char_spam"
	{
		"fi"		"Sinun chat-viestisi on estetty (Syy: Wide-Char Spam)."
	}

	"kick_bad_name"
	{
		"fi"		"Sinun nimesi sisältää tuntemattomia merkkejä. Vaihda nimesi"
	}

	"ban_name_newline"
	{
		"fi"		"Nimen Newlineja havaittu"
	}
}


================================================
FILE: translations/fr/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"fr"		"Ce serveur est protégé par Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"fr"		"Erreur: Echec de la réponse à une requète, redémarrez votre jeu si cette erreur persiste"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"fr"		"Exploit détecté: Votre interp est trop élevé ({1}ms / {2}ms max). Fixez votre cl_interp à {3} ou moins"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"fr"		"Votre ping est trop élevé ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"fr"		"Vous avez été bannis du serveur"
	}

	"ban_angle"
	{
		"fr"		"Angle-Cheats détecté"
	}

	"ban_chat_clear"
	{
		"fr"		"Suppression du chat détecté (chat-clear)"
	}

	"ban_convar"
	{
		"fr"		"ConVar invalide détecté"
	}

	"ban_bhop"
	{
		"fr"		"Bhop détecté"
	}

	"ban_aimbot"
	{
		"fr"		"Aimbot détecté"
	}

	"ban_aimlock"
	{
		"fr"		"Aimlock détecté"
	}

	"ban_anti_duck_delay"
	{
		"fr" 		"Anti délai d'accroupissement détecté"
	}

	"ban_noisemaker"
	{
		"fr" 		"Spam deGénérateur de bruit infini détecté"
	}
}


================================================
FILE: translations/hu/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"hu"		"Ezen szerver a Little Anti-Cheat {1} védelme alatt áll."
	}

	"kick_query_failure"
	{
		"hu"		"Lekérdezés válasz hiba. Kérlek indítsd újra a játékot, amennyiben ez nem szűnik meg."
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"hu"		"Túl magas az interp-ed ({1}ms), a maximum megengedett érték: {2}ms. Kérlek állítsd át {3}-ra vagy kevesebbre. (Pl.: cl_interp 0)"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"hu"		"A pinged túl magas. ({1}) A maximális megengedett érték: {2}."
	}

	"kick_ban_generic"
	{
		"hu"		"Banolva lettél erről a szerverről."
	}

	"ban_angle"
	{
		"hu"		"Angle-Cheat Detektálva."
	}

	"ban_chat_clear"
	{
		"hu"		"Chat-Clear Detektálva."
	}

	"ban_convar"
	{
		"hu"		"Invalid ConVar Detektálva."
	}

	"ban_bhop"
	{
		"hu"		"Bhop Detektálva."
	}

	"ban_aimbot"
	{
		"hu"		"Aimbot Detektálva."
	}

	"ban_aimlock"
	{
		"hu"		"Aimlock Detektálva."
	}

	"ban_anti_duck_delay"
	{
		"hu" 		"Anti-Duck-Delay Detektálva."
	}

	"ban_noisemaker"
	{
		"hu" 		"Infinite Noisemaker Detektálva."
	}
}


================================================
FILE: translations/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"en"		"This server is protected by Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"en"		"Error: Query response failure, please restart your game if this issue persists"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"en"		"Exploit detected: Your interp is too high ({1}ms / {2}ms max). Please set your cl_interp back to {3} or lower"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"en"		"Your ping is too high ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"en"		"You have been banned from this server"
	}

	"ban_angle"
	{
		"en"		"Angle-Cheats Detected"
	}

	"ban_chat_clear"
	{
		"en"		"Chat-Clear Detected"
	}

	"ban_convar"
	{
		"en"		"Invalid ConVar Detected"
	}

	"ban_nolerp"
	{
		"en"		"NoLerp Detected"
	}

	"ban_bhop"
	{
		"en"		"Bhop Detected"
	}

	"ban_aimbot"
	{
		"en"		"Aimbot Detected"
	}

	"ban_aimlock"
	{
		"en"		"Aimlock Detected"
	}

	"ban_anti_duck_delay"
	{
		"en" 		"Anti-Duck-Delay Detected"
	}

	"ban_noisemaker"
	{
		"en" 		"Infinite Noise Maker Detected"
	}

	"ban_macro"
	{
		"en"		"Macro Detected"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"en"		"{1} Macro is not allowed on this server"
	}

	"chat_invalid_characters"
	{
		"en" 		"Your chat message has been blocked (Reason: Invalid Characters)."
	}

	"chat_wide_char_spam"
	{
		"en"		"Your chat message has been blocked (Reason: Wide-Char Spam)."
	}

	"kick_bad_name"
	{
		"en"		"Your name contains invalid characters, please change your name"
	}

	"ban_name_newline"
	{
		"en"		"Name Newlines Detected"
	}

	"admin_chat_warning_generic"
	{
		"#format"	"{1:s},{2:s},{3:d}"
		"en"		"{1} is suspected of using '{2}' (Detections: {3})"
	}
}


================================================
FILE: translations/lv/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"lv"		"Šis serveris izmanto Little Anti-Cheat aizsardzību {1}"
	}

	"kick_query_failure"
	{
		"lv"		"Kļūda: Query response failure, Restartē spēli ja šī problēma turpinās"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"lv"		"Exploits atklāts: Tavs interp ir pārāk liels ({1}ms / {2}ms max). Lūdzu uzstādi cl_interp atpakaļ uz {3} vai zemāk"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"lv"		"Tavs pings ir pārāk liels ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"lv"		"Tu tiki banots no šī servera"
	}

	"ban_angle"
	{
		"lv"		"Angle-Cheats Atklāts"
	}

	"ban_chat_clear"
	{
		"lv"		"Chat-Clear Atklāts"
	}

	"ban_convar"
	{
		"lv"		"Invalid ConVar Atklāts"
	}

	"ban_nolerp"
	{
		"lv"		"NoLerp Atklāts"
	}

	"ban_bhop"
	{
		"lv"		"Bhop Atklāts"
	}

	"ban_aimbot"
	{
		"lv"		"Aimbot Atklāts"
	}

	"ban_aimlock"
	{
		"lv"		"Aimlock Atklāts"
	}

	"ban_anti_duck_delay"
	{
		"lv" 		"Anti-Duck-Delay Atklāts"
	}

	"ban_noisemaker"
	{
		"lv" 		"Infinite Noise Maker Atklāts"
	}

	"ban_macro"
	{
		"lv"		"Macro Atklāts"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"lv"		"{1} Macro skriptus aizliegts lietot šajā serverī"
	}

	"chat_invalid_characters"
	{
		"lv" 		"Tavas čata ziņas tika bloķētas (Iemesls: Invalid Characters)."
	}

	"chat_wide_char_spam"
	{
		"lv"		"Tavas čata ziņas tika bloķētas (Iemesls: Wide-Char Spam)."
	}

	"kick_bad_name"
	{
		"lv"		"Tavs nickname satur invalid characters, lūdzu nomaini to"
	}

	"ban_name_newline"
	{
		"lv"		"Name Newlines Atklāts"
	}

	"admin_chat_warning_generic"
	{
		"#format"	"{1:s},{2:s},{3:d}"
		"lv"		"{1} Tiek turēts aizdomās par '{2}' (Detections: {3})"
	}
}


================================================
FILE: translations/nl/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"nl"		"Deze server is beveiligd met Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"nl"		"Error: Fout bij opvragen van antwoord, start je game opnieuw als dit probleem zich blijft voordoen"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"nl"		"Exploit gedetecteerd: Je interp is te hoog ({1}ms / {2}ms max). Zet cl_interp terug naar {3} of lager"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"nl"		"Je ping is te hoog ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"nl"		"Je bent verbannen van deze server"
	}

	"ban_angle"
	{
		"nl"		"Ongeldige kijkrichting gedetecteerd"
	}

	"ban_chat_clear"
	{
		"nl"		"Verwijdering van chat gedetecteerd"
	}

	"ban_convar"
	{
		"nl"		"Ongeldige ConVar gedetecteerd"
	}

	"ban_bhop"
	{
		"nl"		"Bhop gedetecteerd"
	}

	"ban_aimbot"
	{
		"nl"		"Aimbot gedetecteerd"
	}

	"ban_aimlock"
	{
		"nl"		"Aimlock gedetecteerd"
	}

	"ban_anti_duck_delay"
	{
		"nl"		"Snelle squat gedetecteerd"
	}

	"ban_noisemaker"
	{
		"nl"		"Oneindige noisemaker gedetecteerd"
	}
}


================================================
FILE: translations/no/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"no"		"Denne serveren er beskyttet av Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"no"		"Error: Spillet svarte ikke på forespørsler i tide. Start spillet på nytt om problemet fortsetter"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"no"		"Exploit oppdaget: Interpen din er for høy ({1}ms / {2}ms maks). Sett ned din cl_interp til {3} eller lavere"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"no"		"Nettverk forsinkelsen (Ping) din er for høy ({1} / {2} maks)"
	}

	"kick_ban_generic"
	{
		"no"		"Du har blitt utestengt fra denne serveren på grunn av juksing"
	}

	"ban_angle"
	{
		"no"		"Ugyldig synsrettning oppdaget"
	}

	"ban_chat_clear"
	{
		"no"		"Fjerning av chat oppdaget"
	}

	"ban_convar"
	{
		"no"		"Ugyldig console verdi oppdaget"
	}

	"ban_bhop"
	{
		"no"		"Bhop oppdaget"
	}

	"ban_aimbot"
	{
		"no"		"Aimbot oppdaget"
	}

	"ban_aimlock"
	{
		"no"		"Aimlock oppdaget"
	}

	"ban_anti_duck_delay"
	{
		"no" 		"Antidukk-forsinkelse oppdaget"
	}

	"ban_noisemaker"
	{
		"no" 		"Uendelig Noisemaker-bruk oppdaget"
	}

	"ban_macro"
	{
		"no"		"Macro oppdaget"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"no"		"{1} Macro er ikke tillatt på denne serveren"
	}

	"chat_invalid_characters"
	{
		"no" 		"Chat melding blokkert, fjern ugyldige bokstaver og prøv igjen"
	}

	"chat_wide_char_spam"
	{
		"no"		"Chat melding blokkert, lang-bokstav spam oppdaget"
	}

	"kick_bad_name"
	{
		"no"		"Navnet ditt har ugyldige bokstaver, vennligst endre navnet ditt"
	}

	"ban_name_newline"
	{
		"no"		"Ny-linje bokstav i navnet oppdaget"
	}

	"admin_chat_warning_generic"
	{
		"#format"	"{1:s},{2:s},{3:d}"
		"no"		"{1} mistenkes for å bruke '{2}' (Oppdagelser: {3})"
	}
}


================================================
FILE: translations/pl/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"pl"		"Ten serwer jest chroniony przez Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"pl"		"Błąd: Błąd odpowiedzi na zapytanie, zrestartuj grę jeśli problem będzie się powtarzał"
	}

	"kick_interp_exploit"
	{
		"pl"		"Wykryto exploita: Twój interp jest za wysoki ({1}ms / {2}ms maks). Ustaw cl_interp z powrotem na {3} lub niższy"
	}

	"tban_ping_high"
	{
		"pl"		"Twój ping jest za wysoki ({1} / {2} maks)"
	}

	"kick_ban_generic"
	{
		"pl"		"Zostałeś zbanowany na tym serwerze"
	}

	"ban_angle"
	{
		"pl"		"Wykryto Angle-Cheats"
	}

	"ban_chat_clear"
	{
		"pl"		"Wykryto Chat-Clear"
	}

	"ban_convar"
	{
		"pl"		"Wykryto Nieprawidłowy ConVar"
	}

	"ban_bhop"
	{
		"pl"		"Wykryto Bhop"
	}

	"ban_aimbot"
	{
		"pl"		"Wykryto Aimbot"
	}

	"ban_aimlock"
	{
		"pl"		"Wykryto Aimlock"
	}

	"ban_anti_duck_delay"
	{
		"pl"		"Wykryto Anti-Duck-Delay"
	}

	"ban_noisemaker"
	{
		"pl"		"Wykryto Nieskończony Zagłuszacz"
	}
}


================================================
FILE: translations/pt/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"pt"		"Este servidor é protegido pelo Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"pt"		"Erro: o seu cliente falhou ao responder o pedido. Reinicie o seu jogo caso este problema persista"
	}

	"kick_interp_exploit"
	{
		"pt"		"Brecha detectada: a sua interpolação está muito alta ({1}ms / máx. {2}ms). Defina a variável do console \"cl_interp\" de volta para {3} ou um valor menor"
	}

	"tban_ping_high"
	{
		"pt"		"A sua latência está muito alta ({1} / máx. {2})"
	}

	"kick_ban_generic"
	{
		"pt"		"Você foi banido(a) deste servidor"
	}

	"ban_angle"
	{
		"pt"		"Trapaças relacionadas a ângulos detectadas"
	}

	"ban_chat_clear"
	{
		"pt"		"Limpador da janela de conversa detectado"
	}

	"ban_convar"
	{
		"pt"		"Variável do console inválida detectada"
	}
	
	"ban_nolerp"
	{
		"pt"		"Anulação de interpolação detectada"
	}

	"ban_bhop"
	{
		"pt"		"Bunny hop detectado"
	}

	"ban_aimbot"
	{
		"pt"		"Auxílio de mira (aimbot) detectado"
	}

	"ban_aimlock"
	{
		"pt"		"Auxílio de mira (aimlock) detectado"
	}
	
	"ban_anti_duck_delay"
	{
		"pt" 		"Remoção do atraso para agachar detectado"
	}

	"ban_noisemaker"
	{
		"pt" 		"Brinquedo Barulhento infinito detectado"
	}

	"ban_macro"
	{
		"pt"		"Macro detectado"
	}

	"kick_macro"
	{
		"pt"		"O macro do tipo \"{1}\" não é permitido neste servidor"
	}

	"chat_invalid_characters"
	{
		"pt" 		"A sua mensagem na janela de conversa foi bloqueada por conter caracteres inválidos."
	}

	"chat_wide_char_spam"
	{
		"pt"		"A sua mensagem na janela de conversa foi bloqueada por conter caracteres de spam espaçosos."
	}

	"kick_bad_name"
	{
		"pt"		"O seu nome contém caracteres inválidos. Altere-o"
	}

	"ban_name_newline"
	{
		"pt"		"Nome com quebras de linha detectado"
	}

	"admin_chat_warning_generic"
	{
		"pt"		"{1} possivelmente está usando \"{2}\". (Nº de detecções: {3}.)"
	}
}


================================================
FILE: translations/ro/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"ro"		"Acest server este protejat de Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"ro"		"Eroare: Interogare eșuată, te rog sa repornești jocul dacă această problemă persistă"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"ro"		"Exploit detectat: interp-ul tău este prea mare ({1}ms / {2}ms max). Te rog sa îți setezi cl_interp înapoi la {3} sau mai mic"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"ro"		"Ping-ul tău este prea mare ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"ro"		"Ai fost banat pe acest server"
	}

	"ban_angle"
	{
		"ro"		"Angle-Cheats Detectat"
	}

	"ban_chat_clear"
	{
		"ro"		"Chat-Clear Detectat"
	}

	"ban_convar"
	{
		"ro"		"Invalid ConVar Detectat"
	}

	"ban_nolerp"
	{
		"ro"		"NoLerp Detectat"
	}

	"ban_bhop"
	{
		"ro"		"Bhop Detectat"
	}

	"ban_aimbot"
	{
		"ro"		"Aimbot Detectat"
	}

	"ban_aimlock"
	{
		"ro"		"Aimlock Detectat"
	}

	"ban_anti_duck_delay"
	{
		"ro" 		"Anti-Duck-Delay Detectat"
	}

	"ban_noisemaker"
	{
		"ro" 		"Infinite Noise Maker Detectat"
	}

	"ban_macro"
	{
		"ro"		"Macro Detectat"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"ro"		"{1} Macro nu este permis pe acest server"
	}

	"chat_invalid_characters"
	{
		"ro" 		"Mesajul tău din chat a fost blocat (Motiv: Caractere Invalide)."
	}

	"chat_wide_char_spam"
	{
		"ro"		"Mesajul tău din chat a fost blocat (Motiv: Spam cu caractere largi)."
	}

	"kick_bad_name"
	{
		"ro"		"Numele tău conține caractere invalide, te rog să iți schimbi numele."
	}

	"ban_name_newline"
	{
		"ro"		"Au fost detectate noi linii în nume"
	}

	"admin_chat_warning_generic"
	{
		"#format"	"{1:s},{2:s},{3:d}"
		"ro"		"{1} este suspect de folosire  '{2}' (Detecții: {3})"
	}
}


================================================
FILE: translations/ru/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"ru"		"Этот сервер защищён Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"ru"		"Ошибка: клиент не отвечает на запросы сервера, перезапустите игру если проблема продолжается"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"ru"		"Обнаружен эксплоит: Слишком большое значение interp ({1}ms / {2}ms max). Пожалуйста установите cl_interp на {3} или ниже"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"ru"		"Ваш пинг слишком большой ({1} / {2} макс)"
	}

	"kick_ban_generic"
	{
		"ru"		"Вы были забанены на этом сервере"
	}

	"ban_angle"
	{
		"ru"		"Обнаружены недопустимые углы обзора"
	}

	"ban_chat_clear"
	{
		"ru"		"Обнаружено очищение чата"
	}

	"ban_convar"
	{
		"ru"		"Обнаружено недопустимое значение консольной переменной"
	}

	"ban_bhop"
	{
		"ru"		"Обнаружен Bhop"
	}

	"ban_aimbot"
	{
		"ru"		"Обнаружен Aimbot"
	}

	"ban_aimlock"
	{
		"ru"		"Обнаружен Aimlock"
	}

	"ban_anti_duck_delay"
	{
		"ru" 		"Обнаружен обход задержки приседания"
	}
}


================================================
FILE: translations/sv/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"se"		"Denna server är säkrad av Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"se"		"Fel: Frågesvar misslyckades, var god starta om ditt spel om detta problem fortsätter."
	}

	"kick_interp_exploit"
	{
		"se"		"Exploit Upptäckt: Din interp är för hög ({1}ms / {2}ms max). Var god sätt din cl_interp tillbaka till {3} eller lägre"
	}

	"tban_ping_high"
	{
		"se"		"Din ping är för hög ({1} / {2} max)"
	}

	"kick_ban_generic"
	{
		"se"		"Du har blivit bannlyst från denna server"
	}

	"ban_angle"
	{
		"se"		"Vinkel-Fusk Upptäckt"
	}

	"ban_chat_clear"
	{
		"se"		"Chatt-Rensning Upptäckt"
	}

	"ban_convar"
	{
		"se"		"Ogiltig ConVar Upptäckt"
	}

	"ban_nolerp"
	{
		"se"		"NoLerp Upptäckt"
	}

	"ban_bhop"
	{
		"se"		"Bhop Upptäckt"
	}

	"ban_aimbot"
	{
		"se"		"Aimbot Upptäckt"
	}

	"ban_aimlock"
	{
		"se"		"Aimlock Upptäckt"
	}

	"ban_anti_duck_delay"
	{
		"se" 		"Anti-Duck-Fördröjning Upptäckt"
	}

	"ban_noisemaker"
	{
		"se" 		"Oändlig Noisemaker Upptäckt"
	}

	"ban_macro"
	{
		"se"		"Macro Upptäckt"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"se"		"{1} Macro är inte tillåtet i denna server."
	}

	"chat_invalid_characters"
	{
		"se" 		"Chatt meddelande blockerad, ogiltiga karaktärer upptäckt."
	}

	"chat_wide_char_spam"
	{
		"se"		"Chatt meddelande blockerad, bred-karaktär spam upptäckt."
	}

	"kick_bad_name"
	{
		"se"		"Ditt namn innehåller ogiltiga karaktärer, var god ändra ditt namn."
	}

	"ban_name_newline"
	{
		"se"		"Namn-radbrytningar Upptäckt"
	}
}


================================================
FILE: translations/tr/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"tr"		"Bu sunucu Little Anti-Cheat {1} tarafından korunmaktadır"
	}

	"kick_query_failure"
	{
		"tr"		"Hata: Query yanıtı hatası, bu sorun devam ederse lütfen oyununuzu yeniden başlatın"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"tr"		"Exploit Algılandı: Interp çok yüksek (en fazla {1}ms / {2}ms). Lütfen cl_interp değerinizi {3} veya daha düşük bir değere ayarlayın"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"tr"		"Pinginiz çok yüksek (en fazla {1} / {2})"
	}

	"kick_ban_generic"
	{
		"tr"		"Bu sunucudan yasaklandınız"
	}

	"ban_angle"
	{
		"tr"		"Angle-Cheats Algılandı"
	}

	"ban_chat_clear"
	{
		"tr"		"Chat-Clear Algılandı"
	}

	"ban_convar"
	{
		"tr"		"Geçersiz ConVar Tespit Edildi"
	}

	"ban_nolerp"
	{
		"tr"		"NoLerp Algılandı"
	}

	"ban_bhop"
	{
		"tr"		"Bhop Algılandı"
	}

	"ban_aimbot"
	{
		"tr"		"Aimbot Algılandı"
	}

	"ban_aimlock"
	{
		"tr"		"Aimlock Algılandı"
	}

	"ban_anti_duck_delay"
	{
		"tr" 		"FastDuck Algılandı"
	}

	"ban_noisemaker"
	{
		"tr" 		"Sonsuz Gürültücü Tespit Edildi"
	}

	"ban_macro"
	{
		"tr"		"Makro Algılandı"
	}

	"kick_macro"
	{
		"#format"	"{1:s}"
		"tr"		"{1} Bu sunucuda makroya izin verilmiyor"
	}

	"chat_invalid_characters"
	{
		"tr" 		"Sohbet mesajınız engellendi (Nedeni: Geçersiz Karakterler)."
	}

	"chat_wide_char_spam"
	{
		"tr"		"Sohbet mesajınız engellendi (Nedeni: Geniş Karakterli Spam)."
	}

	"kick_bad_name"
	{
		"tr"		"Adınız geçersiz karakterler içeriyor, lütfen adınızı değiştirin"
	}

	"ban_name_newline"
	{
		"tr"		"Adınızda Yeni Satır Algılandı"
	}
}


================================================
FILE: translations/ua/lilac.phrases.txt
================================================
"Phrases"
{
	"welcome_msg"
	{
		"#format"	"{1:s}"
		"ua"		"Цей сервер захищений Little Anti-Cheat {1}"
	}

	"kick_query_failure"
	{
		"ua"		"Помилка: клієнт не відповідає на запити сервера, перезапустіть гру якщо це буде продовжуватись"
	}

	"kick_interp_exploit"
	{
		"#format"	"{1:.0f},{2:d},{3:.3f}"
		"ua"		"виявлено експлоїт: Занадто велике значення interp ({1}ms / {2}ms max). Будь ласка встановіть cl_interp на {3} або нижче"
	}

	"tban_ping_high"
	{
		"#format"	"{1:.0f},{2:d}"
		"ua"		"Ваш пінг занадто великий ({1} / {2} макс)"
	}

	"kick_ban_generic"
	{
		"ua"		"Ви були забанені на цьому сервері"
	}

	"ban_angle"
	{
		"ua"		"Виявлено неприпустимі кути огляду"
	}

	"ban_chat_clear"
	{
		"ua"		"Виявлено очищення чату"
	}

	"ban_convar"
	{
		"ua"		"Виявлено неприпустиме значення консольної змінної"
	}

	"ban_bhop"
	{
		"ua"		"Виявлено Bhop"
	}

	"ban_aimbot"
	{
		"ua"		"Виявлено Aimbot"
	}

	"ban_aimlock"
	{
		"ua"		"Виявлено Aimlock"
	}
}


================================================
FILE: updatefile.txt
================================================
"Updater"
{
	"Information"
	{
		"Version"
		{
			"Latest"	"1.7.4"
		}

		"Notes" "Removed the need to download other third party includes (SB, SB++, MA and Updater)."
		"Notes" "Removed Angle checks in L4D1&2."
		"Notes" "Added define in lilac.sp for TF2Classic support."
		"Notes" "Added support for the old version of SourceBans."
		"Notes" "Added admin chat warnings for suspicious players (Aimbot, Aimlock and Bhop)."
		"Notes" "Fixed a bug where multiple aimbot checks are fired upon getting multiple kills from the same shot."
		"Notes" "Removed custom warnings when compiling."
		"Notes" "Updated sourcecode to compile in SM 1.11 without warnings."
	}

	"Files"
	{
		"Plugin"	"Path_SM/plugins/lilac.smx"
		"Plugin"	"Path_SM/translations/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/chi/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/cze/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/de/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/es/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/fi/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/fr/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/nl/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/no/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/pt/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/ru/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/tr/lilac.phrases.txt"
		"Plugin"	"Path_SM/translations/ua/lilac.phrases.txt"
		"Source"	"Path_SM/scripting/lilac.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_globals.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_aimbot.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_aimlock.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_angles.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_anti_duck_delay.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_backtrack.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_bhop.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_config.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_convar.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_lerp.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_macro.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_noisemaker.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_ping.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_stock.sp"
		"Source"	"Path_SM/scripting/lilac/lilac_string.sp"
	}
}
Download .txt
gitextract_9cluogkd/

├── .github/
│   └── FUNDING.yml
├── Changelog
├── README.md
├── plugins/
│   └── lilac.smx
├── scripting/
│   ├── include/
│   │   └── convar_class.inc
│   ├── lilac/
│   │   ├── lilac_aimbot.sp
│   │   ├── lilac_aimlock.sp
│   │   ├── lilac_angles.sp
│   │   ├── lilac_anti_duck_delay.sp
│   │   ├── lilac_backtrack.sp
│   │   ├── lilac_bhop.sp
│   │   ├── lilac_config.sp
│   │   ├── lilac_convar.sp
│   │   ├── lilac_database.sp
│   │   ├── lilac_globals.sp
│   │   ├── lilac_lerp.sp
│   │   ├── lilac_macro.sp
│   │   ├── lilac_noisemaker.sp
│   │   ├── lilac_ping.sp
│   │   ├── lilac_stock.sp
│   │   └── lilac_string.sp
│   └── lilac.sp
├── translations/
│   ├── chi/
│   │   └── lilac.phrases.txt
│   ├── cze/
│   │   └── lilac.phrases.txt
│   ├── da/
│   │   └── lilac.phrases.txt
│   ├── de/
│   │   └── lilac.phrases.txt
│   ├── es/
│   │   └── lilac.phrases.txt
│   ├── fi/
│   │   └── lilac.phrases.txt
│   ├── fr/
│   │   └── lilac.phrases.txt
│   ├── hu/
│   │   └── lilac.phrases.txt
│   ├── lilac.phrases.txt
│   ├── lv/
│   │   └── lilac.phrases.txt
│   ├── nl/
│   │   └── lilac.phrases.txt
│   ├── no/
│   │   └── lilac.phrases.txt
│   ├── pl/
│   │   └── lilac.phrases.txt
│   ├── pt/
│   │   └── lilac.phrases.txt
│   ├── ro/
│   │   └── lilac.phrases.txt
│   ├── ru/
│   │   └── lilac.phrases.txt
│   ├── sv/
│   │   └── lilac.phrases.txt
│   ├── tr/
│   │   └── lilac.phrases.txt
│   └── ua/
│       └── lilac.phrases.txt
└── updatefile.txt
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (212K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 719,
    "preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
  },
  {
    "path": "Changelog",
    "chars": 12202,
    "preview": "1.7.4\n- Removed the need to download other third party includes (SB, SB++, MA and Updater).\n- Removed Angle checks in L4"
  },
  {
    "path": "README.md",
    "chars": 8873,
    "preview": "# Closing notes\nI wish to thank everyone who participated in this project, it's been an interesting ride.\\\nAs fun as it "
  },
  {
    "path": "scripting/include/convar_class.inc",
    "chars": 8010,
    "preview": "#if defined _convar_class_included\n #endinput\n#endif\n#define _convar_class_included\n\n// todo: track previous default val"
  },
  {
    "path": "scripting/lilac/lilac_aimbot.sp",
    "chars": 10881,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_aimlock.sp",
    "chars": 7578,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_angles.sp",
    "chars": 3083,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_anti_duck_delay.sp",
    "chars": 1756,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_backtrack.sp",
    "chars": 3069,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_bhop.sp",
    "chars": 4673,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_config.sp",
    "chars": 31358,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_convar.sp",
    "chars": 3921,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_database.sp",
    "chars": 6306,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2021-2023 azalty\n\tCopyright (C) 2023-2023 J_Tanzanite\n\n\tThis program is free softwa"
  },
  {
    "path": "scripting/lilac/lilac_globals.sp",
    "chars": 7345,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_lerp.sp",
    "chars": 3361,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_macro.sp",
    "chars": 5445,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_noisemaker.sp",
    "chars": 4722,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_ping.sp",
    "chars": 2741,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_stock.sp",
    "chars": 13264,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac/lilac_string.sp",
    "chars": 9318,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "scripting/lilac.sp",
    "chars": 11251,
    "preview": "/*\n\tLittle Anti-Cheat\n\tCopyright (C) 2018-2023 J_Tanzanite\n\n\tThis program is free software: you can redistribute it and/"
  },
  {
    "path": "translations/chi/lilac.phrases.txt",
    "chars": 1265,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"chi\"\t\t\"本服务器受到LAC反作弊系统保护 {1}\"\n\t}\n\n\t\"kick_query_failure\"\n\t{\n\t\t\"chi\"\t\t"
  },
  {
    "path": "translations/cze/lilac.phrases.txt",
    "chars": 930,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"cze\"\t\t\"Tento server je chráněn Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick_qu"
  },
  {
    "path": "translations/da/lilac.phrases.txt",
    "chars": 1582,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"da\"\t\t\"Denne server er beskyttet af Little Anti-Cheat {1}\"\n\t}\n\n\t\"kic"
  },
  {
    "path": "translations/de/lilac.phrases.txt",
    "chars": 1538,
    "preview": "\"Phrases\"\n{\n        \"welcome_msg\"\n        {\n                \"#format\"        \"{1:s}\"\n                \"de\"             \"D"
  },
  {
    "path": "translations/es/lilac.phrases.txt",
    "chars": 1640,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"es\"\t\t\"El servidor está protegido por Little Anti-Cheat {1}\"\n\t}\n\n\t\"k"
  },
  {
    "path": "translations/fi/lilac.phrases.txt",
    "chars": 1579,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"fi\"\t\t\"Tämä palvelin on suojattu Little Anti-Cheatilla {1}\"\n\t}\n\n\t\"ki"
  },
  {
    "path": "translations/fr/lilac.phrases.txt",
    "chars": 1069,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"fr\"\t\t\"Ce serveur est protégé par Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick_"
  },
  {
    "path": "translations/hu/lilac.phrases.txt",
    "chars": 1102,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"hu\"\t\t\"Ezen szerver a Little Anti-Cheat {1} védelme alatt áll.\"\n\t}\n\n"
  },
  {
    "path": "translations/lilac.phrases.txt",
    "chars": 1708,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"en\"\t\t\"This server is protected by Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick"
  },
  {
    "path": "translations/lv/lilac.phrases.txt",
    "chars": 1693,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"lv\"\t\t\"Šis serveris izmanto Little Anti-Cheat aizsardzību {1}\"\n\t}\n\n\t"
  },
  {
    "path": "translations/nl/lilac.phrases.txt",
    "chars": 1084,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"nl\"\t\t\"Deze server is beveiligd met Little Anti-Cheat {1}\"\n\t}\n\n\t\"kic"
  },
  {
    "path": "translations/no/lilac.phrases.txt",
    "chars": 1761,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"no\"\t\t\"Denne serveren er beskyttet av Little Anti-Cheat {1}\"\n\t}\n\n\t\"k"
  },
  {
    "path": "translations/pl/lilac.phrases.txt",
    "chars": 957,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"pl\"\t\t\"Ten serwer jest chroniony przez Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick_query_failure\"\n"
  },
  {
    "path": "translations/pt/lilac.phrases.txt",
    "chars": 1870,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"pt\"\t\t\"Este servidor é protegido pelo Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick_query_failure\"\n\t"
  },
  {
    "path": "translations/ro/lilac.phrases.txt",
    "chars": 1757,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"ro\"\t\t\"Acest server este protejat de Little Anti-Cheat {1}\"\n\t}\n\n\t\"ki"
  },
  {
    "path": "translations/ru/lilac.phrases.txt",
    "chars": 1042,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"ru\"\t\t\"Этот сервер защищён Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick_query_f"
  },
  {
    "path": "translations/sv/lilac.phrases.txt",
    "chars": 1620,
    "preview": "\"Phrases\"\r\n{\r\n\t\"welcome_msg\"\r\n\t{\r\n\t\t\"se\"\t\t\"Denna server är säkrad av Little Anti-Cheat {1}\"\r\n\t}\r\n\r\n\t\"kick_query_failure\""
  },
  {
    "path": "translations/tr/lilac.phrases.txt",
    "chars": 1615,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"tr\"\t\t\"Bu sunucu Little Anti-Cheat {1} tarafından korunmaktadır\"\n\t}\n"
  },
  {
    "path": "translations/ua/lilac.phrases.txt",
    "chars": 956,
    "preview": "\"Phrases\"\n{\n\t\"welcome_msg\"\n\t{\n\t\t\"#format\"\t\"{1:s}\"\n\t\t\"ua\"\t\t\"Цей сервер захищений Little Anti-Cheat {1}\"\n\t}\n\n\t\"kick_query_"
  },
  {
    "path": "updatefile.txt",
    "chars": 2328,
    "preview": "\"Updater\"\r\n{\r\n\t\"Information\"\r\n\t{\r\n\t\t\"Version\"\r\n\t\t{\r\n\t\t\t\"Latest\"\t\"1.7.4\"\r\n\t\t}\r\n\r\n\t\t\"Notes\" \"Removed the need to download "
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the J-Tanzanite/Little-Anti-Cheat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (184.5 KB), approximately 59.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!