#include <amxmodx>
#include <amxmisc>
#include <fakemeta>

// set this to 1 to get some debug messages
#define	DEBUG_MODE	0

// turn this off to stop list from being sorted by keywords in alphabetic order
#define	ALLOW_SORT	1

// Array Defines, ATTENTION: ( MAX_RANDOM + 1 ) * TOK_LENGTH must be smaller 2048 !!!
#define BUFFER_LEN    2048 // Maximum number of space per line to use when reading configfile
#define MAX_RANDOM    15  // Maximum number of tries to find a sound file
#define TOK_LENGTH    60  // Maximum length of keyword and sound file strings
#define MAX_BANS      32  // Maximum number of bans stored
#define NUM_PER_LINE  6   // Number of words per line from amx_sound_help

//#pragma dynamic 16384
#pragma dynamic 65536

#define ACCESS_ADMIN	ADMIN_LEVEL_A

#define PLUGIN_AUTHOR		"White Panther, Luke Sankey, HunteR"
#define PLUGIN_VERSION		"1.8.4"

new Enable_Sound[] =  "misc/woohoo.wav"   // Sound played when Sank Soounds being enabled
new Disable_Sound[] = "misc/awwcrap.wav"  // Sound played when Sank Soounds being disabled

new config_filename[128]

new SndCount[33] = {0, ...}               // Holds the number telling how many sounds a player has played
new Float:SndLenghtCount[33] = {0.0, ...}
new SndOn[33] = {1, ...}

new SND_WARN = 0                          // The number at which a player will get warned for playing too many sounds
new SND_MAX = 0                           // The number at which a player will get muted for playing too many sounds
new Float:SND_MAX_DUR = 0.0
new Float:SND_DELAY = 0.0                 // Minimum delay between sounds
new SND_MODE = 15                         // Determinates who can play and who can hear sounds (dead and alive)
new SND_IMMUNITY = ACCESS_ADMIN           // Determine the access levels which shall have immunity to warn/ban (default ACCESS_ADMIN for backwards compatability)
new EXACT_MATCH = 1                       // Determinates if plugin triggers on exact match, or partial speech match
new ADMINS_ONLY = 0                       // Determinates if only admins are allowed to play sounds
new DISPLAY_KEYWORDS = 1                  // Determinates if keywords are shown in chat or not

new Float:NextSoundTime                   // spam protection
new Float:Join_exit_SoundTime             // spam protection 2
new Float:LastSoundTime = 0.0
new bSoundsEnabled = 1                    // amx_sound <on/off> or <1/0>

new CVAR_freezetime, CVAR_obey_duration

new g_max_players
new banned_player_steamids[MAX_BANS][60]
new restrict_playing_sounds[33]
new sound_quota_steamids[33][60]

new motd_sound_list_address[256]

enum
{
	PARSE_SND_MAX,
	PARSE_SND_MAX_DUR,
	PARSE_SND_WARN,
	PARSE_SND_DELAY,
	PARSE_SND_MODE,
	PARSE_SND_IMMUNITY,
	PARSE_EXACT_MATCH,
	PARSE_ADMINS_ONLY,
	PARSE_DISPLAY_KEYWORDS,
	PARSE_KEYWORD
}

enum
{
	ERROR_NONE,
	ERROR_MAX_KEYWORDS,
	ERROR_STRING_LENGTH
}

enum
{
	RESULT_OK = 1,
	RESULT_BAD_ALIVE_STATUS = 0,
	RESULT_QUOTA_EXCEEDED = -1,
	RESULT_QUOTA_DURATION_EXCEEDED = -2,
	RESULT_SOUND_DELAY = -3,
	RESULT_ADMINS_ONLY = -4,
}

enum
{
	FLAG_IGNORE_AMOUNT = 1,
	FLAGS_JOIN_SND = 2,
	FLAGS_EXIT_SND = 4
}

enum
{
	SOUND_TYPE_SPEECH,
	SOUND_TYPE_MP3,
	SOUND_TYPE_WAV,
	SOUND_TYPE_WAV_LOCAL
}

enum _:SOUND_DATA_BASE
{
	KEYWORD[TOK_LENGTH],
	ADMIN_LEVEL_BASE,
	SOUND_AMOUNT,
	FLAGS,
	PLAY_COUNT_KEY,
	Array:SUB_INDEX
}
enum _:SOUND_DATA_SUB
{
	SOUND_FILE[TOK_LENGTH],
	Float:DURATION,
	ADMIN_LEVEL,
	SOUND_TYPE,
	PLAY_COUNT
}
new Array:soundData

public plugin_init( )
{
	register_plugin("Sank Sounds Plugin", PLUGIN_VERSION, PLUGIN_AUTHOR)
	register_cvar("sanksounds_version", PLUGIN_VERSION, FCVAR_SERVER)
	set_cvar_string("sanksounds_version", PLUGIN_VERSION)
	
	register_concmd("amx_sound_reset", "amx_sound_reset", ACCESS_ADMIN, " <jatekos | all> : Hang kvota torlese a felhasznalo ^"user^", vagy mindenki ^"all^" szamara.")
	register_concmd("amx_sound_add", "amx_sound_add", ACCESS_ADMIN, " <kulcsszo> <dir/sound> : Egy Szo/Hang hozzaadasa a listahoz.")
	register_clcmd("amx_sound_help", "amx_sound_help")
	register_concmd("amx_sound", "amx_sound", ACCESS_ADMIN, " :  Hangok be es kikapcsolasa")
	register_concmd("amx_sound_play", "amx_sound_play", ACCESS_ADMIN, " <dir/sound> : Hang lejatszasa minden felhasznalo szamara.")
	register_concmd("amx_sound_reload", "amx_sound_reload", ACCESS_ADMIN, " : Az snd-list.cfg vagy a custom.cfg ujratoltese. A fajlnev opcionalis. Ha nincs fajlnev, az alap toltodik be")
	register_concmd("amx_sound_remove", "amx_sound_remove", ACCESS_ADMIN, " <kulcsszo> <dir/sound> : Egy szo/wav/mp3 torlese a listabol. Fontos az idezojel.")
	register_concmd("amx_sound_write", "amx_sound_write", ACCESS_ADMIN, " :  Beleir a jelenlegi hang-configuracios fajlba.")
	register_concmd("amx_sound_debug", "amx_sound_debug", ACCESS_ADMIN, "Kiirja a teljes szo/hang listat.")
	register_concmd("amx_sound_ban", "amx_sound_ban", ACCESS_ADMIN, " <nev vagy #userid>: Megtiltja a jatekosnak a hang lejátszasat ezen a palyan.")
	register_concmd("amx_sound_unban", "amx_sound_unban", ACCESS_ADMIN, " <nev vagy #userid>: Engedelyezi a jatekosnak a hang lejatszasat ezen a palyan.")
	register_concmd("amx_sound_top", "amx_sound_top", ACCESS_ADMIN, " <szam> (optional): Megmutatja a leggyakrabban lejatszott <x> kulcsszot (alap 10) ezen a palyan.")	
	register_clcmd("say", "HandleSay")
	register_clcmd("say_team", "HandleSay")
	
	register_cvar("mp_sank_sounds_download", "1")
	CVAR_freezetime = register_cvar("mp_sank_sounds_freezetime", "0")
	CVAR_obey_duration = register_cvar("mp_sank_sounds_obey_duration", "1")
	register_cvar("mp_sank_sounds_motd_address", "")
	
	g_max_players = get_maxplayers()
	
	soundData = ArrayCreate(SOUND_DATA_BASE)
}

public plugin_cfg( )
{
	get_cvar_string("mp_sank_sounds_motd_address", motd_sound_list_address, 255)
	
	new configpath[61]
	get_configsdir(configpath, 60)
	format(config_filename, 127, "%s/SND-LIST.CFG", configpath)	// Name of file to parse
	
	// check if file in capital letter exists
	// otherwise make it all lowercase and try to load it
	if ( file_exists(config_filename) )
	{
		parse_sound_file(config_filename)
	}else
	{
		strtolower(config_filename)
		parse_sound_file(config_filename)
	}
}

public client_putinserver( id )
{
	restrict_playing_sounds[id] = -1
	
	new steamid[60], i
	get_user_authid(id, steamid, 59)
	for ( i = 0; i < MAX_BANS; ++i )
	{
		if ( equal(steamid, banned_player_steamids[i]) )
			restrict_playing_sounds[id] = i
	}
	
	if ( !equal(steamid, sound_quota_steamids[id]) )
	{
		copy(sound_quota_steamids[id], 59, steamid)
		SndCount[id] = 0
		SndLenghtCount[id] = 0.0
	}
	
	SndOn[id] = 1
	
	new Float:gametime = get_gametime()
	if ( gametime <= get_pcvar_num(CVAR_freezetime) )
		return
	
	new sData[SOUND_DATA_BASE]
	ArrayGetArray(soundData, 0, sData)
	if ( sData[SOUND_AMOUNT] == 0 )
		return
	
	if ( Join_exit_SoundTime >= gametime )
		return
	
	if ( sData[SOUND_AMOUNT] == 0 )
		return
	
	new rand = random(sData[SOUND_AMOUNT])
	new subData[SOUND_DATA_SUB]
	ArrayGetArray(sData[SUB_INDEX], rand, subData)
	
	if ( subData[ADMIN_LEVEL] != 0
		&& !(get_user_flags(id) & subData[ADMIN_LEVEL]) )
		return
	
	playsoundall(subData[SOUND_FILE], subData[SOUND_TYPE])
	
	Join_exit_SoundTime = gametime + subData[DURATION]
	if ( NextSoundTime < Join_exit_SoundTime )
		NextSoundTime = Join_exit_SoundTime
}

public client_disconnect( id )
{
	SndOn[id] = 1
	restrict_playing_sounds[id] = -1
	
	new Float:gametime = get_gametime()
	if ( gametime <= get_pcvar_num(CVAR_freezetime) )
		return
	
	new sData[SOUND_DATA_BASE]
	ArrayGetArray(soundData, 1, sData)
	if ( sData[SOUND_AMOUNT] == 0 )
		return
	
	if ( Join_exit_SoundTime >= gametime )
		return
	
	if ( sData[SOUND_AMOUNT] == 0 )
		return
	
	new rand = random(sData[SOUND_AMOUNT])
	new subData[SOUND_DATA_SUB]
	ArrayGetArray(sData[SUB_INDEX], rand, subData)
	
	if ( subData[ADMIN_LEVEL] != 0
		&& !(get_user_flags(id) & subData[ADMIN_LEVEL]) )
		return
	
	playsoundall(subData[SOUND_FILE], subData[SOUND_TYPE])
	
	Join_exit_SoundTime = gametime + subData[DURATION]
	if ( NextSoundTime < Join_exit_SoundTime )
		NextSoundTime = Join_exit_SoundTime
}

public amx_sound_reset( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new arg[33], target
	read_argv(1, arg, 32)
	if ( equal(arg, "all") == 1 )
	{
		client_print(id, print_console, "Sank Sounds >> A Kvota torolve minden jatekosnal")
		for ( target = 1; target <= g_max_players; ++target )
		{
			SndCount[target] = 0
			SndLenghtCount[target] = 0.0
		}
	}else
	{
		target = cmd_target(id, arg, 1)
		if ( !target )
			return PLUGIN_HANDLED
		
		SndCount[target] = 0
		SndLenghtCount[target] = 0.0
		new name[33]
		get_user_name(target, name, 32)
		client_print(id, print_console, "Sank Sounds >> A Kvota torolve lett ennel a jatekosnal: ^"%s^"", name)
	}
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Adds a Word/Sound combo to the list. If it is a valid line in the config
// file, then it is a valid parameter here. The only difference is you can
// only specify one Sound file at a time with this command.
//
// Usage: amx_sound_add <keyword> <dir/sound>
// Usage: amx_sound_add <setting> <value>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_add( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new Word[TOK_LENGTH + 1], Sound[TOK_LENGTH + 1]
	new configOption = 0
	
	read_argv(1, Word, TOK_LENGTH)
	read_argv(2, Sound, TOK_LENGTH)
	if ( strlen(Word) <= 0
		|| strlen(Sound) == 0 )
	{
		client_print(id, print_console, "Sank Sounds >>Ervenytelen formatum")
		client_print(id, print_console, "Sank Sounds >>Hasznalat: amx_sound_add kulcsszo <dir/sound>")
		
		return PLUGIN_HANDLED
	}
	
	// First look for special parameters
	if ( equali(Word, "SND_MAX") )
	{
		SND_MAX = str_to_num(Sound)
		configOption = 1
	}else if ( equali(Word, "SND_MAX_DUR") )
	{
		SND_MAX_DUR = floatstr(Sound)
		configOption = 1
	}else if ( equali(Word, "SND_WARN") )
	{
		SND_WARN = str_to_num(Sound)
		configOption = 1
	}else if ( equali(Word, "SND_DELAY") )
	{
		SND_DELAY = floatstr(Sound)
		configOption = 1
	}else if ( equali(Word, "SND_MODE") )
	{
		SND_MODE = str_to_num(Sound)
		configOption = 1
	}else if ( equali(Word, "SND_IMMUNITY") )
	{
		SND_IMMUNITY = str_to_num(Sound)
		configOption = 1
	}else if ( equali(Word, "EXACT_MATCH") )
	{
		EXACT_MATCH = str_to_num(Sound)
		configOption = 1
	}else if ( equali(Word, "ADMINS_ONLY") )
	{
		ADMINS_ONLY = str_to_num(Sound)
		configOption = 1
	}else if ( equali(Word, "DISPLAY_KEYWORDS") )
	{
		DISPLAY_KEYWORDS = str_to_num(Sound)
		configOption = 1
	}
	
	if ( configOption )
	{
		// Do some error checking on the user-input numbers
		ErrorCheck()
		
		return PLUGIN_HANDLED
	}
	
	// Loop once for each keyword
	new i, j
	new sData[SOUND_DATA_BASE]
	new subData[SOUND_DATA_SUB]
	new aLen = ArraySize(soundData)
	new subLen
	new resCode
	for( i = 0; i < aLen; ++i )
	{
		ArrayGetArray(soundData, i, sData)
		// If no match found, keep looping
		if ( !equal(Word, sData[KEYWORD], TOK_LENGTH) )
			continue
		
		// See if the Sound already exists
		subLen = ArraySize(sData[SUB_INDEX])
		for( j = 0; j < subLen; ++j )
		{
			ArrayGetArray(sData[SUB_INDEX], j, subData)
			// See if this is the same as the new Sound
			if ( equali(Sound, subData[SOUND_FILE], TOK_LENGTH) )
			{
				client_print(id, print_console, "Sank Sounds >> ^"%s; %s^" mar letezik", Word, Sound)
				
				return PLUGIN_HANDLED
			}
		}
		
		// Word exists, but Sound is new to the list, so add entry
		resCode = array_add_inner_element(i, j, Sound, 0)
		if ( resCode != -1 )
			client_print(id, print_console, "Sank Sounds >> ^"%s^" hang sikeresen hozzaadva a ^"%s^" szohoz", Sound, Word)
		
		return PLUGIN_HANDLED
	}
	
	// Word/Sound combo is new to the list, so make a new entry
	array_add_element(i, Word)
	resCode = array_add_inner_element(i, 0, Sound, 0)
	if ( resCode != -1 )
	{
		ArraySort(soundData, "sortSoundDataFunc")
		client_print(id, print_console, "Sank Sounds >> ^"%s; %s^" sikeresen hozzaadva", Word, Sound)
	}else
	{
		// removed keyword because no sound could be added
		array_remove(i)
	}
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// amx_sound_help lists all amx_sound commands and keywords to the user.
//
// Usage: amx_sound_help
//////////////////////////////////////////////////////////////////////////////
public amx_sound_help( id )
{
	print_sound_list(id)
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Turns on/off the playing of the Sound files for this plugin only
//////////////////////////////////////////////////////////////////////////////
public amx_sound( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new onoff[5]
	read_argv(1, onoff, 4)
	if ( equal(onoff, "on")
		|| equal(onoff, "1") )
	{
		if ( bSoundsEnabled == 1 )
			console_print(id, "Sank Sounds >> A Plugin mar be van kapcsolva")
		else
		{
			bSoundsEnabled = 1
			console_print(id, "Sank Sounds >> A Plugin bekapcsolva")
			client_print(0, print_chat, "Sank Sounds >> A Plugin be van kapcsolva")
			if ( Enable_Sound[0] )
			{
				new type = Enable_Sound[0] == '^"' ? SOUND_TYPE_SPEECH : ( Enable_Sound[strlen(Enable_Sound) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV )
				playsoundall(Enable_Sound, type)
			}
		}
		
		return PLUGIN_HANDLED
	}else if ( equal(onoff, "off")
		|| equal(onoff, "0") )
	{
		if ( bSoundsEnabled == 0 )
			console_print(id, "Sank Sounds >> A Plugin mar ki van kapcsolva")
		else
		{
			bSoundsEnabled = 0
			console_print(id, "Sank Sounds >> A Plugin kikapcsolva")
			client_print(0, print_chat, "Sank Sounds >> A Plugin ki van kapcsolva")
			if ( Disable_Sound[0] )
			{
				new type = Disable_Sound[0] == '^"' ? SOUND_TYPE_SPEECH : ( Disable_Sound[strlen(Disable_Sound) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV )
				playsoundall(Disable_Sound, type)
			}
		}
	}
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Plays a sound to all players
//
// Usage: amx_sound_play <dir/sound>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_play( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new arg[128]
	read_argv(1, arg, 127)
	
	if ( strlen(arg) < 1 )
	{
		client_print(id, print_console, "Sank Sounds >> A hang ervenytelen.")
		
		return PLUGIN_HANDLED
	}
	
	new type = arg[0] == '^"' ? SOUND_TYPE_SPEECH : ( arg[strlen(arg) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV )
	playsoundall(arg, type)
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Reloads the Word/Sound combos from filename
//
// Usage: amx_sound_reload <filename>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_reload( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 0) )
		return PLUGIN_HANDLED
	
	new parsefile[128]
	read_argv(1, parsefile, 127)
	// Initialize sound_data array
	array_clear()
	soundData = ArrayCreate(SOUND_DATA_BASE)
	
	parse_sound_file(parsefile, 0)
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Removes a Word/Sound combo from the list. You must specify a keyword, but it
// is not necessary to specify a Sound if you want to remove all Sounds associated
// with that keyword
//
// Usage: amx_sound_remove <keyWord> <dir/sound>"
//////////////////////////////////////////////////////////////////////////////
public amx_sound_remove( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new Word[TOK_LENGTH + 1], Sound[TOK_LENGTH + 1]
	
	read_argv(1, Word, TOK_LENGTH)
	read_argv(2, Sound, TOK_LENGTH)
	if ( strlen(Word) == 0 )
	{
		client_print(id, print_console, "Sank Sounds >> Ervenytelen formatum")
		client_print(id, print_console, "Sank Sounds >> Hasznalat: amx_sound_remove kulcsszo <dir/sound>")
		
		return PLUGIN_HANDLED
	}
	
	// speech must have extra ""
	if ( strlen(Sound) != 0
		&& containi(Sound, ".wav") == -1
		&& containi(Sound, ".mp") == -1 )
		format(Sound, TOK_LENGTH, "^"%s^"", Sound)
	
	// Loop once for each keyWord
	new iCurWord, jCurSound
	new sData[SOUND_DATA_BASE]
	new subData[SOUND_DATA_SUB]
	new aLen = ArraySize(soundData)
	new subLen
	for( iCurWord = 0; iCurWord < aLen; ++iCurWord )
	{
		ArrayGetArray(soundData, iCurWord, sData)
		// Look for a Word match
		if ( !equali(Word, sData[KEYWORD], TOK_LENGTH) )
			continue
		
		// If no Sound was specified, then remove the whole Word's entry
		if ( strlen(Sound) == 0 )
		{
			array_remove(iCurWord)
			
			client_print(id, print_console, "Sank Sounds >> %s szo sikeresen torolve", Word)
			
			return PLUGIN_HANDLED
		}
		
		// Just remove the one Sound, if it exists
		subLen = ArraySize(sData[SUB_INDEX])
		for( jCurSound = 0; jCurSound < subLen; ++jCurSound )
		{
			ArrayGetArray(sData[SUB_INDEX], iCurWord, subData)
			// Look for a Sound match
			if ( !equali(Sound, subData[SOUND_FILE], TOK_LENGTH) )
				continue
			
			if ( sData[SOUND_AMOUNT] == 1 )		// If this is the only Sound entry, then remove the entry altogether
			{
				array_remove(iCurWord)
				
				client_print(id, print_console, "Sank Sounds >> %s szo sikeresen torolve", Word)
			}else
			{
				array_remove_inner(iCurWord, jCurSound)
				
				client_print(id, print_console, "Sank Sounds >> %s hang sikeresen torolve lett a %s szorol", Sound, Word)
			}
			
			return PLUGIN_HANDLED
		}
		// We reached the end for this Word, and the Sound didn't exist
		client_print(id, print_console, "Sank Sounds >> %s hang nem talalhato", Sound)
		
		return PLUGIN_HANDLED
	}
	// We reached the end, and the Word didn't exist
	client_print(id, print_console, "Sank Sounds >> %s nem talalhato", Word)
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Saves the current configuration of Word/Sound combos to filename for possible
// reloading at a later time. You cannot overwrite the default file.
//
// Usage: amx_sound_write <filename>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_write( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new savefile[128]
	
	read_argv(1, savefile, 127)
	if ( strlen(savefile) <= 1 )
	{
		if ( strlen(savefile) == 1
			&& savefile[0] == '!' )
			copy(savefile, 127, config_filename)
		else
		{
			client_print(id, print_console, "Sank Sounds >> Nem adtal meg fajlnevet. To save to default configfile use ! olyan fajlnev/parameter.")
			return PLUGIN_HANDLED
		}
	}
	
	new TimeStamp[128], name[33], Text[64]
	new Textlen = 63
	get_user_name(id, name, 32)
	get_time("%H:%M:%S %A %B %d, %Y", TimeStamp, 127)
	
	new file = fopen(savefile, "w+")
	if ( !file )
	{
		log_amx("Sank Sounds >> Nem sikerult a ^"%s^" fajl beolvasasa", savefile)
		
		return PLUGIN_HANDLED
	}
	
	formatex(Text, Textlen, "# Idopont:^t^t%s^n", TimeStamp)
	fputs(file, Text)
	formatex(Text, Textlen, "# Fajlt letrehozta:^t%s^n", name)
	fputs(file, Text)
	fputs(file, "^n")		// blank line
	fputs(file, "# Fontos parameterek:^n")
	formatex(Text, Textlen, "SND_MAX;^t^t%d^n", SND_MAX)
	fputs(file, Text)
	formatex(Text, Textlen, "SND_MAX_DUR;^t^t%.1f^n", SND_MAX_DUR)
	fputs(file, Text)
	formatex(Text, Textlen, "SND_WARN;^t^t%d^n", SND_WARN)
	fputs(file, Text)
	
	new sData[SOUND_DATA_BASE]
	new subData[SOUND_DATA_SUB]
	ArrayGetArray(soundData, 0, sData);
	new subLen = ArraySize(sData[SUB_INDEX])
	fputs(file, "SND_JOIN;^t^t")
	for ( new j = 0; j < subLen; ++j )
	{
		ArrayGetArray(sData[SUB_INDEX], j, subData);
		cfg_write_keysound(file, subData)
	}
	fputc(file, '^n')
	ArrayGetArray(soundData, 1, sData);
	subLen = ArraySize(sData[SUB_INDEX])
	fputs(file, "SND_EXIT;^t^t")
	for ( new j = 0; j < subLen; ++j )
	{
		ArrayGetArray(sData[SUB_INDEX], j, subData);
		cfg_write_keysound(file, subData)
	}
	fputc(file, '^n')
	formatex(Text, Textlen, "SND_DELAY;^t^t%f^n", SND_DELAY)
	fputs(file, Text)
	formatex(Text, Textlen, "SND_MODE;^t^t%d^n", SND_MODE)
	fputs(file, Text)
	new snd_imm_str[32]
	get_flags(SND_IMMUNITY, snd_imm_str, 26)
	formatex(Text, Textlen, "SND_IMMUNITY;^t^t^"%s^"^n", snd_imm_str)
	fputs(file, Text)
	formatex(Text, Textlen, "EXACT_MATCH;^t^t%d^n", EXACT_MATCH)
	fputs(file, Text)
	formatex(Text, Textlen, "ADMINS_ONLY;^t^t%d^n", ADMINS_ONLY)
	fputs(file, Text)
	formatex(Text, Textlen, "DISPLAY_KEYWORDS;^t%d^n", DISPLAY_KEYWORDS)
	fputs(file, Text)
	fputs(file, "^n")		// blank line
	fputs(file, "# Word/Sound combinations:^n")
	
	new aLen = ArraySize(soundData)
	new j
	for ( new i = 2; i < aLen; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		ArrayGetArray(soundData, i, sData);
		
		cfg_write_keyword(file, sData)
		subLen = ArraySize(sData[SUB_INDEX])
		for ( j = 0; j < subLen; ++j )
		{
			ArrayGetArray(sData[SUB_INDEX], j, subData);
			cfg_write_keysound(file, subData)
		}
		fputc(file, '^n')
	}
	
	fclose(file)
	
	client_print(id, print_console, "Sank Sounds >> %s sikeresen hozzaadva a configuraciohoz", savefile)
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Prints out Word/Sound combo matrix for debugging purposes. Kinda cool, even
// if you're not really debugging.
//
// Usage: amx_sound_debug
// Usage: amx_sound_reload <filename>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_debug( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 1)
		&& id > 0 )
		return PLUGIN_HANDLED
	
	new i, j, join_snd_buff[BUFFER_LEN], exit_snd_buff[BUFFER_LEN]
	
	if ( !is_dedicated_server()
		&& id == 1 )	// for listenserver and with id = 1 we can use server_print
		id = 0
	
	if ( id )
		client_print(id, print_console, "SND_WARN: %d^nSND_MAX: %d^nSND_MAX_DUR: %5.1f^n", SND_WARN, SND_MAX, SND_MAX_DUR)
	else
		server_print("SND_WARN: %d^nSND_MAX: %d^nSND_MAX_DUR: %5.1f", SND_WARN, SND_MAX, SND_MAX_DUR)
	
	new sData[SOUND_DATA_BASE]
	new subData[SOUND_DATA_SUB]
	new aLen = ArraySize(soundData)
	new subLen
	ArrayGetArray(soundData, 0, sData)
	subLen = ArraySize(sData[SUB_INDEX])
	new tempstr[TOK_LENGTH]
	for( i = 0; i < subLen; ++i )
	{
		ArrayGetArray(sData[SUB_INDEX], i, subData)
		formatex(tempstr, TOK_LENGTH, "%s;", subData[SOUND_FILE])
		add(join_snd_buff, BUFFER_LEN, tempstr)
	}
	ArrayGetArray(soundData, 1, sData)
	subLen = ArraySize(sData[SUB_INDEX])
	for( i = 0; i < subLen; ++i )
	{
		ArrayGetArray(sData[SUB_INDEX], i, subData)
		formatex(tempstr, TOK_LENGTH, "%s;", subData[SOUND_FILE])
		add(exit_snd_buff, BUFFER_LEN, tempstr)
	}
	
	new snd_imm_str[32]
	get_flags(SND_IMMUNITY, snd_imm_str, 26)
	if ( id )
	{
		client_print(id, print_console, "SND_JOIN: %s", join_snd_buff)
		client_print(id, print_console, "SND_EXIT: %s", exit_snd_buff)
		client_print(id, print_console, "SND_DELAY: %f^nSND_MODE: %d^nSND_IMMUNITY: %s^nEXACT_MATCH: %d", SND_DELAY, SND_MODE, snd_imm_str, EXACT_MATCH)
		client_print(id, print_console, "ADMINS_ONLY: %d^nDISPLAY_KEYWORDS: %d", ADMINS_ONLY, DISPLAY_KEYWORDS)
	}else
	{
		server_print("SND_JOIN: %s", join_snd_buff)
		server_print("SND_EXIT: %s", exit_snd_buff)
		server_print("SND_DELAY: %f^nSND_MODE: %d^nSND_IMMUNITY: %s^nEXACT_MATCH: %d", SND_DELAY, SND_MODE, snd_imm_str, EXACT_MATCH)
		server_print("ADMINS_ONLY: %d^nDISPLAY_KEYWORDS: %d", ADMINS_ONLY, DISPLAY_KEYWORDS)
	}
	
	// Print out the matrix of sound data, so we got what we think we did
	for( i = 2; i < aLen; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		ArrayGetArray(soundData, i, sData)
		
		new access_level[32]
		get_flags(sData[ADMIN_LEVEL_BASE], access_level, 31)
		if ( id )
			client_print(id, print_console, "^n[%d] ^"%s^" %d hanggal %s es ^"%s^" szinttel (lejatsszva: %d)", i - 2, sData[KEYWORD], sData[SOUND_AMOUNT], sData[SOUND_AMOUNT] > 1 ? "s" : "", access_level, sData[PLAY_COUNT_KEY])
		else
			server_print("^n[%d] ^"%s^" %d hanggal %s es ^"%s^" szinttel (lejatsszva: %d)", i - 2, sData[KEYWORD], sData[SOUND_AMOUNT], sData[SOUND_AMOUNT] > 1 ? "s" : "", access_level, sData[PLAY_COUNT_KEY])
		subLen = ArraySize(sData[SUB_INDEX])
		for( j = 0; j < subLen; ++j )
		{
			ArrayGetArray(sData[SUB_INDEX], j, subData)
			
			get_flags(subData[ADMIN_LEVEL], access_level, 31)
			if ( id )
				client_print(id, print_console, " ^"%s^" - ido: %5.2f - admin szint ^"%s^" (lejatsszva: %d)", subData[SOUND_FILE], subData[DURATION], access_level, subData[PLAY_COUNT])
			else
				server_print(" ^"%s^" - ido: %5.2f - admin szint ^"%s^" (lejatsszva: %d)", subData[SOUND_FILE], subData[DURATION], access_level, subData[PLAY_COUNT])
		}
	}
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Bans players from using sounds for current map
//
// Usage: amx_sound_ban <player>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_ban( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new arg[33]
	read_argv(1, arg, 32)
	new player = cmd_target(id, arg, 1)
	if ( !player )
		return PLUGIN_HANDLED
	
	if ( get_user_flags(player) & (SND_IMMUNITY | ACCESS_ADMIN) )
		return PLUGIN_HANDLED
	
	if ( restrict_playing_sounds[player] == -1 )
	{
		new found, empty = -1
		new steamid[60]
		get_user_authid(id, steamid, 59)
		for ( new i = 0; i < MAX_BANS; ++i )
		{
			if ( empty == -1
				&& !banned_player_steamids[i][0] )
				empty = i
			
			if ( !equal(steamid, banned_player_steamids[i]) )
				continue
			
			found = 1
			
			break
		}
		if ( !found )
		{
			if ( empty == -1 )
				empty = 0
			
			copy(banned_player_steamids[empty], 59, steamid)
			
			restrict_playing_sounds[player] = empty
		}
	}
	
	new name[33]
	get_user_name(player, name, 32)
	client_print(id, print_console, "Sank Sounds >> ^"%s^" nevu jatekos kitiltva a hangok lejatszasa miatt", name)
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Unbans players from using sounds for current map
//
// Usage: amx_sound_unban <player>
//////////////////////////////////////////////////////////////////////////////
public amx_sound_unban( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 2) )
		return PLUGIN_HANDLED
	
	new arg[33]
	read_argv(1, arg, 32)
	new player = cmd_target(id, arg)
	if ( !player )
		return PLUGIN_HANDLED
	
	if ( restrict_playing_sounds[player] != -1 )
	{
		new found = -1
		new steamid[60]
		get_user_authid(id, steamid, 59)
		for ( new i = 0; i < MAX_BANS; ++i )
		{
			if ( !equal(steamid, banned_player_steamids[i]) )
				continue
			
			found = i
			
			break
		}
		if ( found != -1 )
			banned_player_steamids[found][0] = 0
		
		restrict_playing_sounds[player] = -1
	}
	
	new name[33]
	get_user_name(player, name, 32)
	client_print(id, print_console, "Sank Sounds >> ^"%s^" nevu jatekos feloldva a hangok lejatszasa alol", name)
	
	return PLUGIN_HANDLED
}

public amx_sound_top( id , level , cid )
{
	if ( !cmd_access(id, level, cid, 1) )
		return PLUGIN_HANDLED
	
	new arg[33]
	read_argv(1, arg, 32)
	new topX = 10
	if ( strlen(arg) > 0 )
		topX = str_to_num(arg)
	
	if ( topX < 1
		|| topX > 50 )
	{
		client_print(id, print_console, "Sank Sounds >> Allits be egy erteket 1 - 50 kozott vagy hagyd uresen")
		return PLUGIN_HANDLED
	}
	
	new topIDs[50] = {-1, ...}
	new topCount[50] = {0, ...}
	new sData[SOUND_DATA_BASE]
	new aLen = ArraySize(soundData)
	for ( new keyIndex = 0; keyIndex < aLen; ++keyIndex )
	{
		ArrayGetArray(soundData, keyIndex, sData)
		
		for ( new i = 0; i < topX; ++i )
		{
			if ( sData[PLAY_COUNT_KEY] <= topCount[i] )
				continue
			
			// copy all other down
			for ( new j = topX - 1; j > i; --j )
			{
				topIDs[j] = topIDs[j - 1]
				topCount[j] = topCount[j - 1]
			}
			topIDs[i] = keyIndex
			topCount[i] = sData[PLAY_COUNT_KEY]
			break
		}
	}
	new text[512]
	new counter = 0
	client_print(id, print_console, "Sank Sounds >> Top %d:", topX)
	while ( counter < topX )
	{
		if ( topIDs[counter] != -1 )
		{
			ArrayGetArray(soundData, topIDs[counter], sData)
			format(text, 511, "%s(%d) %s^n", text, topCount[counter], sData[KEYWORD])
		}else
			counter = topX - 1
		if ( (counter % 10 == 0
				&& counter != 0 )
			|| counter == topX - 1 )
		{
			client_print(id, print_console, text)
			text[0] = 0
		}
		++counter
	}
	
	return PLUGIN_HANDLED
}

//////////////////////////////////////////////////////////////////////////////
// Everything a person says goes through here, and we determine if we want to
// play a sound or not.
//
// Usage: say <anything>
//////////////////////////////////////////////////////////////////////////////
public HandleSay( id )
{
	// If sounds are not enabled, then skip this whole thing
	if ( !bSoundsEnabled )
		return PLUGIN_CONTINUE
	
	// player is banned from playing sounds
	if ( restrict_playing_sounds[id] != -1 )
		return PLUGIN_CONTINUE
	
	new Speech[128]
	read_args(Speech, 127)
	remove_quotes(Speech)
	
	// credit to SR71Goku for fixing this oversight:
	new speachLen = strlen(Speech)
	if ( !speachLen )
		return PLUGIN_CONTINUE
	
	if ( equal(Speech, "/sound", 6) )
	{
		if ( Speech[6] == 's' )
		{
			if ( Speech[7] == 'o'
				&& Speech[8] == 'n'
				&& Speech[9] == 0 )
			{
				SndOn[id] = 1
				client_print(id, print_chat, "[WorldSniper] Ismét hallhatod a hangokat!")
			}else if ( Speech[7] == 'o'
				&& Speech[8] == 'f'
				&& Speech[9] == 'f'
				&& Speech[10] == 0 )
			{
				SndOn[id] = 0
				client_print(id, print_chat, "[WorldSniper]Önnek a hangok lejátszása le lett tiltva!")
			}else if ( Speech[7] == 0 )
				print_sound_list(id, 1)
			else
				return PLUGIN_CONTINUE
			
			return PLUGIN_HANDLED
		}else if ( Speech[6] == 'l'
			&& Speech[7] == 'i'
			&& Speech[8] == 's'
			&& Speech[9] == 't'
			&& Speech[10] == 0 )
		{
			print_sound_list(id, 1)
			
			return PLUGIN_HANDLED
		}
		
		return PLUGIN_CONTINUE
	}
	
	new ListIndex = -1
	new pinToLocation = (Speech[speachLen - 1] == '!')
	new sData[SOUND_DATA_BASE]
	new subData[SOUND_DATA_SUB]
	new aLen = ArraySize(soundData)
	// Check to see if what the player said is a trigger for a sound
	for ( new i = 2; i < aLen; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		ArrayGetArray(soundData, i, sData)
		
		if ( equali(Speech, sData[KEYWORD])
			|| (EXACT_MATCH == 1
				&& pinToLocation == 1
				&& speachLen == strlen(sData[KEYWORD]) + 1
				&& equali(Speech, sData[KEYWORD], speachLen - 1) )
			|| ( EXACT_MATCH == 0
				&& containi(Speech, sData[KEYWORD]) != -1 ) )
		{
			// check for access
			if ( sData[ADMIN_LEVEL_BASE] == 0
				|| get_user_flags(id) & sData[ADMIN_LEVEL_BASE] )
				ListIndex = i
			
			break
		}
	}
	
	// check If player used NO sound trigger
	if ( ListIndex == -1 )
		return PLUGIN_CONTINUE
	
	if ( sData[SOUND_AMOUNT] == 0 )
		return PLUGIN_CONTINUE
	
	new obey_duration_mode = get_pcvar_num(CVAR_obey_duration)
	new Float:gametime = get_gametime()
	new allowedToPlay = isUserAllowed2Play(id, gametime, obey_duration_mode)
	if ( allowedToPlay == RESULT_OK )
	{
		displayQuotaWarning(id)
		new rand
		new timeout
		new playFile[TOK_LENGTH]
		
		// This for loop runs around until it finds a real file to play
		// Defaults to the first Sound file, if no file is found at random.
		for( timeout = MAX_RANDOM;			// Initial condition
			timeout >= 0 && !strlen(playFile);	// While these are true
			--timeout )				// Update each iteration
		{
			rand = random(sData[SOUND_AMOUNT])
			// If for some reason we never find a file
			//  then default to the first Sound entry
			if ( !timeout )
				rand = 0
			
			ArrayGetArray(sData[SUB_INDEX], rand, subData)
			// check if sound has access defined, if so only allow admins to use it
			if ( subData[ADMIN_LEVEL] == 0
				|| ( get_user_flags(id) & subData[ADMIN_LEVEL] ) )
				copy(playFile, TOK_LENGTH, subData[SOUND_FILE])
		}
		
		if ( playFile[0] )
		{
			NextSoundTime = gametime + subData[DURATION]
			
			// Increment their playsound count
			++SndCount[id]
			SndLenghtCount[id] += subData[DURATION]
			
			// increment counter
			++sData[PLAY_COUNT_KEY]
			++subData[PLAY_COUNT]
			
			new type = subData[SOUND_TYPE]
			if ( pinToLocation == 1
				&& type == SOUND_TYPE_WAV )
				type = SOUND_TYPE_WAV_LOCAL
			playsoundall(playFile, type, SND_MODE & 16, is_user_alive(id))
			
			LastSoundTime = gametime
			ArraySetArray(sData[SUB_INDEX], rand, subData)
			ArraySetArray(soundData, ListIndex, sData)
		}
	}else if ( allowedToPlay == RESULT_QUOTA_EXCEEDED
		|| allowedToPlay == RESULT_QUOTA_DURATION_EXCEEDED )
	{
		if ( !displayQuotaExceeded(id) )
		{
			if ( allowedToPlay == RESULT_SOUND_DELAY
				&& obey_duration_mode != 0 )
				client_print(id, print_chat, "Sank Sounds >> Egy hangot mar lejatsszottal ( varnod kell %3.1f masodpercet )", NextSoundTime + SND_DELAY - gametime)
			else if ( allowedToPlay != RESULT_QUOTA_EXCEEDED
				&& allowedToPlay != RESULT_QUOTA_DURATION_EXCEEDED )
				client_print(id, print_chat, "Sank Sounds >> Ne jatssz le olyan gyakran ( varj %3.1f masodpercet )", LastSoundTime + SND_DELAY - gametime)
		}
	}
	
	if ( DISPLAY_KEYWORDS == 0 )
		return PLUGIN_HANDLED
	
	return PLUGIN_CONTINUE
}

//////////////////////////////////////////////////////////////////////////////
// Parses the sound file specified by loadfile. If loadfile is empty, then
// it parses the default config_filename.
//////////////////////////////////////////////////////////////////////////////
parse_sound_file( loadfile[] , precache_sounds = 1 )
{
	if ( !strlen(loadfile) )
		copy(loadfile, 127, config_filename)
	
	if ( !file_exists(loadfile) )
	{
		// file does not exist
		log_amx("Sank Sounds >> Nem talalhato a ^"%s^" fajl", loadfile)
		
		return
	}
	
	new current_package_str[4]
	new current_package, package_num
	if ( vaultdata_exists("sank_sounds_current_package") )
	{
		get_vaultdata("sank_sounds_current_package", current_package_str, 3)
		current_package = str_to_num(current_package_str)
	}
	
	new allowed_to_precache = 1, allow_check_existence = 1, allow_to_use_sounds = 1
	new allow_global_precache = get_cvar_num("mp_sank_sounds_download")
	new mapname[32]
	get_mapname(mapname, 31)
	
	new i
	new ListIndex = -1
	new tmpIndex = -1
	new maxLineBuf_len = BUFFER_LEN - 1
	new strLineBuf[BUFFER_LEN]
	
	new error_code = ERROR_NONE
	new parse_option = PARSE_KEYWORD
	new temp_str[128]
	new check_for_semi
	new position
	
	new sData[SOUND_DATA_BASE]
	
	new file = fopen(loadfile, "r")
	if ( !file )
	{
		log_amx("Sank Sounds >> Nem sikerult a ^"%s^" fajl beolvasasa", loadfile)

		return
	}
	
	while ( fgets(file, strLineBuf, maxLineBuf_len) )
	{
		if ( (strLineBuf[0] == '^n')						// empty line
			|| ( strLineBuf[0] == 10 && strLineBuf[1] == '^n' )		// empty line
			|| ( strLineBuf[0] == '/' && strLineBuf[1] == '/' )		// comment
			|| (strLineBuf[0] == '#') )					// another comment
			continue
		
		trim(strLineBuf)	// remove newline and spaces
		
		if ( equali(strLineBuf, "package ", 8) )
		{
			++package_num
			if ( current_package )
			{
				if ( current_package == str_to_num(strLineBuf[8]) )
					allowed_to_precache = 1
				else
					allowed_to_precache = 0
			}else
			{
				current_package = 1
				allowed_to_precache = 1
			}
			
			allow_to_use_sounds = 1
			allow_check_existence = 1
			
			continue
		}else if ( equali(strLineBuf, "mapname ", 8) )
		{
			if ( equali(strLineBuf[8], mapname) )
				allowed_to_precache = 1
			else
				allowed_to_precache = 0
			
			allow_to_use_sounds = 1
			allow_check_existence = 1
			
			continue
		}else if ( equali(strLineBuf, "mapnameonly ", 12) )
		{
			if ( equali(strLineBuf[12], mapname) )
			{
				allowed_to_precache = 1
				allow_to_use_sounds = 1
			}else
			{
				allowed_to_precache = 0
				allow_to_use_sounds = 0
			}
			
			allow_check_existence = 1
			
			continue
		}else if ( equali(strLineBuf, "modspecific", 11) )
		{
			allow_to_use_sounds = 1
			allow_check_existence = 0
			
			continue
		}
		
		if ( !allow_to_use_sounds )	// check for sounds that can be used only on specified map
			continue
		
		error_code = ERROR_NONE
		position = 0
		for( i = 0; ; ++i )
		{
			// check if reached end of buffer ( input has been parsed )
			if ( position >= strlen(strLineBuf) )
			{
				strLineBuf[0] = 0
				break
			}
			
			temp_str[0] = 0		// reset
			check_for_semi = contain(strLineBuf[position], ";")
			if ( check_for_semi != -1 )
			{
				copyc(temp_str, 127, strLineBuf[position], ';')
				position += check_for_semi + 1
			}else
			{
				copy(temp_str, 127, strLineBuf[position])
				position += strlen(temp_str)
			}
			
			// Now remove any spaces or tabs from around the strings -- clean them up
			trim(temp_str)
			
			// check if file length is bigger than array
			if ( strlen(temp_str) > TOK_LENGTH )
			{
				error_code = ERROR_STRING_LENGTH
				
				break
			}
			
			if ( i == 0 )
			{	// first entry is not a sound file
				if ( equali(temp_str, "SND_MAX") )
					parse_option = PARSE_SND_MAX
				else if ( equali(temp_str, "SND_MAX_DUR") )
					parse_option = PARSE_SND_MAX_DUR
				else if ( equali(temp_str, "SND_WARN") )
					parse_option = PARSE_SND_WARN
				else if ( equali(temp_str, "SND_DELAY") )
					parse_option = PARSE_SND_DELAY
				else if ( equali(temp_str, "SND_MODE") )
					parse_option = PARSE_SND_MODE
				else if ( equali(temp_str, "SND_IMMUNITY") )
					parse_option = PARSE_SND_IMMUNITY
				else if ( equali(temp_str, "EXACT_MATCH") )
					parse_option = PARSE_EXACT_MATCH
				else if ( equali(temp_str, "ADMINS_ONLY") )
					parse_option = PARSE_ADMINS_ONLY
				else if ( equali(temp_str, "DISPLAY_KEYWORDS") )
					parse_option = PARSE_DISPLAY_KEYWORDS
				else
				{
					parse_option = PARSE_KEYWORD
					if ( ListIndex != -1 )
						ArrayGetArray(soundData, ListIndex, sData)
					if ( ListIndex != -1
						&& sData[SOUND_AMOUNT] == 0
						&& !(sData[FLAGS] & FLAG_IGNORE_AMOUNT) )	// check if allowed to ignore amount of sounds ( eg: SND_JOIN / SND_EXIT )
					{
						log_amx("Sank Sounds >> Ervenyes hang nelkuli kulcsszot talaltam. Atugras erre a kulcsszora: ^"%s^"", sData[KEYWORD])
						array_remove(ListIndex)
					}else
						++ListIndex
					
					new result = array_add_element(ListIndex, temp_str)
					if ( result > -1 )
					{
						tmpIndex = result
						--ListIndex
					}else
					{
						tmpIndex = -1
						if ( result == -1 )
							ListIndex = 2
					}
				}
			}else
			{
				switch ( parse_option )
				{
					case PARSE_SND_MAX:
					{
						SND_MAX = str_to_num(temp_str)
					}
					case PARSE_SND_MAX_DUR:
					{
						SND_MAX_DUR = floatstr(temp_str)
					}
					case PARSE_SND_WARN:
					{
						SND_WARN = str_to_num(temp_str)
					}
					case PARSE_SND_DELAY:
					{
						SND_DELAY = floatstr(temp_str)
					}
					case PARSE_SND_MODE:
					{
						SND_MODE = str_to_num(temp_str)
					}
					case PARSE_SND_IMMUNITY:
					{
						if ( temp_str[0] == '^"' )
						{
							new temp_str2[32]
							copyc(temp_str2, 31, temp_str[1], '^"')
							if ( strlen(temp_str2) == 0 )
								SND_IMMUNITY = (1<<30)
							else
								SND_IMMUNITY = read_flags(temp_str2)
						}else
							SND_IMMUNITY = read_flags(temp_str)
					}
					case PARSE_EXACT_MATCH:
					{
						EXACT_MATCH = str_to_num(temp_str)
					}
					case PARSE_ADMINS_ONLY:
					{
						ADMINS_ONLY = str_to_num(temp_str)
					}
					case PARSE_DISPLAY_KEYWORDS:
					{
						DISPLAY_KEYWORDS = str_to_num(temp_str)
					}
					case PARSE_KEYWORD:
					{
						new error_value = -1
						if ( tmpIndex != -1 )
							error_value = array_add_inner_element(tmpIndex, i - 1, temp_str, allow_check_existence, allow_global_precache, precache_sounds, allowed_to_precache)
						else
							error_value = array_add_inner_element(ListIndex, i - 1, temp_str, allow_check_existence, allow_global_precache, precache_sounds, allowed_to_precache)
						if ( error_value == -1 )
						{
							// sound could not be added
							continue
						}
					}
				}
			}
		}
		
		if ( error_code == ERROR_STRING_LENGTH )
		{
			log_amx("Sank Sounds >> Atugras erre a szo/hang kombinaciora. A Szo vagy Hang tul hosszu: ^"%s^". A hossz %i de max. %i lehet (valtson nevet/torolje a spaceket a configban vagy bovitse a TOK_LENGTH erteket)", temp_str, strlen(temp_str), TOK_LENGTH)
			
			continue
		}
		if ( error_code != ERROR_NONE )
		{
			log_amx("Sank Sounds >> Fatal Error")
			
			continue
		}
		
		// If we finished MAX_RANDOM times, and strLineBuf[position] still has contents
		// then we should have a bigger MAX_RANDOM
		else if ( position < strlen(strLineBuf) )
		{
			log_amx("Sank Sounds >> A hang-lista reszlegesen hibas. Novelje a MAX_RANDOM erteket. Tovabb a fajl feldolgozasara ^"%s^"^n", loadfile)
		}
	}
	
	fclose(file)
	
	if ( ListIndex != -1 )
	{
		ArrayGetArray(soundData, ListIndex, sData)
		if ( sData[SOUND_AMOUNT] == 0
			&& !(sData[FLAGS] & FLAG_IGNORE_AMOUNT) )	// check if allowed to ignore amount of sounds ( eg: SND_JOIN / SND_EXIT )
		{
			log_amx("Sank Sounds >> Ervenyes hang nelkul talalt kulcsszo. Atugras erre a kulcsszora: ^"%s^"", sData[KEYWORD])
			array_remove(ListIndex)
			--ListIndex
		}
	}
	
	// Now we have all of the data from the text file in our data structures.
	// Next we do some error checking, some setup, and we're done parsing!
	ErrorCheck()
	
	++current_package
	if ( current_package > package_num )
		current_package = 1
	
	num_to_str(current_package, current_package_str, 3)
	set_vaultdata("sank_sounds_current_package", current_package_str)
	
	//++ListIndex
#if ALLOW_SORT == 1
	if ( ListIndex > 1 )
		ArraySort(soundData, "sortSoundDataFunc")
#endif
}

//////////////////////////////////////////////////////////////////////////////
// Returns status indicating if user is allowed to play a sound
// or the reason why he is not
//////////////////////////////////////////////////////////////////////////////
isUserAllowed2Play( id , Float:gametime , obey_duration_mode )
{
	// order of checks is important
	
	new admin_flags = get_user_flags(id)
	
	// check if only admins can play sounds
	if ( ADMINS_ONLY
		&& !(admin_flags & ACCESS_ADMIN) )
		return RESULT_ADMINS_ONLY
	
	// check if super admin
	if ( admin_flags & ADMIN_RCON )
	{
		// check if super admin has to obey duration
		if ( !(obey_duration_mode & 4) )
			return RESULT_OK
		return RESULT_SOUND_DELAY
	}
	
	// check if admin
	if ( admin_flags & ACCESS_ADMIN )
	{
		// check if admin has to obey duration
		if ( !(obey_duration_mode & 2) )
			return RESULT_OK
		return RESULT_SOUND_DELAY
	}
	
	if ( SND_MAX != 0
		&& SndCount[id] >= SND_MAX )
		return RESULT_QUOTA_EXCEEDED
	
	if ( SND_MAX_DUR != 0.0
		&& SndLenghtCount[id] > SND_MAX_DUR )
		return RESULT_QUOTA_DURATION_EXCEEDED
	
	// check if player is allowed to play sounds depending on alive config
	if ( !(SND_MODE & (is_user_alive(id) + 1)) )
		return RESULT_BAD_ALIVE_STATUS
	
	// check for sound overlapping + delay time
	if ( gametime > NextSoundTime + SND_DELAY )
		return RESULT_OK
	
	// check if overlapping is allowed
	// or for delay time
	if ( !(obey_duration_mode & 1)
		&& gametime > LastSoundTime + SND_DELAY )
		return RESULT_OK
	
	return RESULT_SOUND_DELAY
}

displayQuotaWarning( id )
{
	new admin_flags = get_user_flags(id)
	if ( (admin_flags & (SND_IMMUNITY | ACCESS_ADMIN)) > 0)
		return
	
	if ( SND_MAX != 0 )
	{
		if ( SndCount[id] >= SND_WARN )
		{
			if ( SndCount[id] + 1 == SND_MAX )
				client_print(id, print_chat, "Sank Sounds >> Ez volt az utolso lejatszhato hangod")
			else
				client_print(id, print_chat, "Sank Sounds >> Van %d masodperced mielott lenemitananak", SND_MAX - SndCount[id] - 1)
		}
	}
}

displayQuotaExceeded( id )
{
	new admin_flags = get_user_flags(id)
	if ( (admin_flags & (SND_IMMUNITY | ACCESS_ADMIN)) > 0)
		return 0
	
	if ( SND_MAX != 0 )
	{
		if ( SndCount[id] >= SND_MAX )
		{
			if ( SndCount[id] - 3 < SND_MAX )
			{
				client_print(id, print_chat, "Sank Sounds >> Figyelmeztettek, le lettel nemitva")

				// player is already muted, we increament here to save a variable to protect player from "you are muted" spam ( only 3 warnings )
				++SndCount[id]
			}
			return 1
		}
	}
	return 0
}

//////////////////////////////////////////////////////////////////////////////
// Checks the input variables for invalid values
//////////////////////////////////////////////////////////////////////////////
ErrorCheck( )
{
	// Can't have negative delay between sounds
	if ( SND_DELAY < 0.0 )
	{
		log_amx("Sank Sounds >> SND_DELAY erteke nem lehet negativ. Allitsd at erre: 0")
		SND_DELAY = 0.0
	}
	
	// If SND_MAX is zero, then sounds quota is disabled. Can't have negative quota
	if ( SND_MAX < 0 )
	{
		SND_MAX = 0	// in case it was negative
		log_amx("Sank Sounds >> SND_MAX erteke nem lehet negativ. Allitsd at erre: 0")
	}
	
	// If SND_MAX_DUR is zero, then sounds quota is disabled. Can't have negative quota
	if ( SND_MAX_DUR < 0.0 )
	{
		SND_MAX_DUR = 0.0	// in case it was negative
		log_amx("Sank Sounds >> SND_MAX_DUR erteke nem lehet negativ. Allitsd at erre: 0.0")
	}
	
	// If SND_WARN is zero, then we can't have warning every time a keyword is said,
	// so we default to 3 less than max
	else if ( ( SND_WARN <= 0 && SND_MAX != 0 )
		|| SND_MAX < SND_WARN )
	{
		if ( SND_MAX < SND_WARN  )
			// And finally, if they want to warn after a person has been
			// muted, that's silly, so we'll fix it.
			log_amx("Sank Sounds >> SND_WARN erteke nem lehet nagyobb SND_MAX ertekenel")
		else if ( SND_WARN <= 0 )
			log_amx("Sank Sounds >> SND_WARN erteke nem lehet nulla")
		
		if ( SND_MAX > 3 )
			SND_WARN = SND_MAX - 3
		else
			SND_WARN = SND_MAX - 1
		
		log_amx("Sank Sounds >> SND_WARN beallitva az alap ertekre: %i", SND_WARN)
	}
}

playsoundall( sound[] , type , split_dead_alive = 0 , sender_alive_status = 0 )
{
	new alive
	for( new i = 1; i <= g_max_players; ++i )
	{
		if ( !is_user_connected(i) )
			continue
		
		if ( is_user_bot(i) )
			continue
		
		if ( !SndOn[i] )
			continue
		
		alive = is_user_alive(i)
		if ( !(SND_MODE & ( alive * 4 + 4 )) )
			continue
		
		if ( split_dead_alive
			&& alive != sender_alive_status		// make sure if splited both are in same group
			&& !(SND_MODE & ( alive * 32 + 32 )) )	// OR check if different groups may hear each other
			continue
		
		if ( type == SOUND_TYPE_MP3 )
			client_cmd(i, "mp3 play ^"%s^"", sound)
		else if ( type == SOUND_TYPE_WAV_LOCAL )
			client_cmd(i, "play ^"%s^"", sound)
		else if ( type == SOUND_TYPE_SPEECH )
			client_cmd(i, "spk %s", sound)
		else
			client_cmd(i, "spk ^"%s^"", sound)
	}
}

print_sound_list( id , motd_msg = 0 )
{
	new text[256], motd_buffer[2048], ilen, skip_for_loop
	new info_text[70] = "Ird be < kulcsszo >: Egy hang lejatsszasahoz. Kulcsszavak listaja:"
	if ( strlen(motd_sound_list_address) > 3 )	// make sure at least you have something like: a.b ( http://a.b )
	{
		copy(motd_buffer, 255, motd_sound_list_address)
		skip_for_loop = 1
		motd_msg = 1
	}else if ( motd_msg )
		ilen = format(motd_buffer, 2047, "<body bgcolor=#000000><font color=#FFB000><pre>%s^n", info_text)
	else
		client_print(id, print_console, info_text)
	
	// Loop once for each keyword
	new i, j = -1
	new sData[SOUND_DATA_BASE]
	new aLen = ArraySize(soundData)
	for ( i = 2; i < aLen && skip_for_loop == 0; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		ArrayGetArray(soundData, i, sData)
		
		// check if player can see admin sounds
		++j
		new found_stricted = 0
		if ( sData[ADMIN_LEVEL_BASE] == 0
			|| get_user_flags(id) & sData[ADMIN_LEVEL_BASE] )
		{
			if ( motd_msg )
				ilen += format(motd_buffer[ilen], 2047 - ilen, "%s", sData[KEYWORD])
			else
				add(text, 255, sData[KEYWORD])
		}else
		{
			--j
			found_stricted = 1
		}
		
		if ( !found_stricted )
		{
			if ( j % NUM_PER_LINE == NUM_PER_LINE - 1 )
			{
				// We got NUM_PER_LINE on this line,
				// so print it and start on the next line
				if ( motd_msg )
					ilen += format(motd_buffer[ilen], 2047 - ilen, "^n")
				else
				{
					client_print(id, print_console, "%s", text)
					text[0] = 0
				}
			}else
			{
				if ( motd_msg )
					ilen += format(motd_buffer[ilen], 2047 - ilen, " | ")
				else
					add(text, 255, " | ")
			}
		}
	}
	if ( motd_msg
		&& strlen(motd_buffer) )
		show_motd(id, motd_buffer)
	else if ( strlen(text) )
		client_print(id, print_console, text)
}

#if ALLOW_SORT == 1
public sortSoundDataFunc( Array:array , item1 , item2 , const data[] , data_size )
{
	new data1[SOUND_DATA_BASE]
	new data2[SOUND_DATA_BASE]
	ArrayGetArray(array, item1, data1)
	ArrayGetArray(array, item2, data2)
	if ( (data1[FLAGS] & FLAGS_JOIN_SND) == FLAGS_JOIN_SND )
		return -1;
	if ( (data2[FLAGS] & FLAGS_JOIN_SND) == FLAGS_JOIN_SND )
		return 1;
	if ( (data1[FLAGS] & FLAGS_EXIT_SND) == FLAGS_EXIT_SND )
		return -1;
	if ( (data2[FLAGS] & FLAGS_EXIT_SND) == FLAGS_EXIT_SND )
		return 1;
	return strcmp(data1[KEYWORD], data2[KEYWORD])
}
#endif

array_add_element( num , keyword[] )
{
	new join_check = equali(keyword, "SND_JOIN")
	new exit_check = equali(keyword, "SND_EXIT")
	// if index is 0 or 1 but not the correct keyword then make sure to save in correct array position
	if ( join_check == 0
		&& exit_check == 0 )
	{
		if ( num == 0
			|| num == 1 )
		{
			join_check = -1
			exit_check = -1
			num = 2
		}
	}else
	{
		if ( num > 1 )
		{
			if ( join_check != 0 )
			{
				num = 0
				exit_check = -1
			}else if ( exit_check != 0 )
			{
				num = 1
				join_check = -1
			}
		}
	}
	
	new sData[SOUND_DATA_BASE]
	if ( join_check > 0 )
		sData[FLAGS] |= FLAGS_JOIN_SND | FLAG_IGNORE_AMOUNT
	if ( exit_check > 0 )
		sData[FLAGS] |= FLAGS_EXIT_SND | FLAG_IGNORE_AMOUNT
	sData[ADMIN_LEVEL_BASE] = cfg_parse_access(keyword)
	copy(sData[KEYWORD], TOK_LENGTH, keyword)
	sData[PLAY_COUNT_KEY] = 0
	sData[SUB_INDEX] = _:ArrayCreate(SOUND_DATA_SUB)
	if ( num < ArraySize(soundData) )
		ArrayInsertArrayBefore(soundData, num, sData)
	else
		ArrayPushArray(soundData, sData)
	
	return (join_check == -1 && exit_check == -1)
		? -1
		: (join_check == -1 || exit_check == -1)
			? num : -2
}

array_add_inner_element( num , elem , soundfile[] , allow_check_existence = 1 , allow_global_precache = 0 , precache_sounds = 0 , allowed_to_precache = 0 )
{
	new subData[SOUND_DATA_SUB]
	subData[ADMIN_LEVEL] = cfg_parse_access(soundfile)
	subData[SOUND_TYPE] = soundfile[0] == '^"' ? SOUND_TYPE_SPEECH : ( soundfile[strlen(soundfile) - 1] == '3' ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV )
	subData[PLAY_COUNT] = 0
	
	// check if not speech sounds
	if ( soundfile[0] != '^"' )
	{
		new sound_file_name[TOK_LENGTH + 1 + 10]
		new is_mp3 = ( containi(soundfile, ".mp3") != -1 )
		new isWav_inSound_folder = 0
		if ( !is_mp3 )
		{	// ".mp3" in not in the string
			if ( equali(soundfile, "sound/", 6) )
			{
				formatex(sound_file_name, TOK_LENGTH + 10, "%s", soundfile)
				isWav_inSound_folder = 1
			}else
				formatex(sound_file_name, TOK_LENGTH + 10, "sound/../%s", soundfile)
		}
		else
		{
			copy(sound_file_name, TOK_LENGTH + 10, soundfile)
		}
		
		if ( allow_check_existence )
		{
			if ( !file_exists(sound_file_name) )
			{
				log_amx("Sank Sounds >> A betoltendo fajl nem letezik. Atugras erre a fajlra: ^"%s^"", sound_file_name)

				return -1
			}
			
			subData[DURATION] = _:cfg_get_duration(sound_file_name, is_mp3 ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV )
			
			if ( subData[DURATION] <= 0.0 )
			{
				log_amx("Sank Sounds >> A hang idotartama nem valos. A Fajl serult. Atugras erre a fajlra: ^"%s^"", sound_file_name)
				
				return -1
			}
		}
		
		if ( allow_global_precache
			&& precache_sounds == 1
			&& allowed_to_precache )
		{
			if ( is_mp3
				|| !isWav_inSound_folder )
				engfunc(EngFunc_PrecacheGeneric, soundfile)
			else
				engfunc(EngFunc_PrecacheSound, soundfile[6])
		}
	}
	
	copy(subData[SOUND_FILE], TOK_LENGTH, soundfile)
	
	new sData[SOUND_DATA_BASE]
	ArrayGetArray(soundData, num, sData)
	++sData[SOUND_AMOUNT]
	if ( elem < ArraySize(sData[SUB_INDEX]) )
		ArrayInsertArrayBefore(sData[SUB_INDEX], elem, subData)
	else
		ArrayPushArray(sData[SUB_INDEX], subData)
	ArraySetArray(soundData, num, sData)
	
	return 1
}

array_clear( )
{
	new sData[SOUND_DATA_BASE]
	new aLen = ArraySize(soundData)
	for ( new i = 0; i < aLen; ++i )
	{
		ArrayGetArray(soundData, i, sData)
		ArrayDestroy(sData[SUB_INDEX])
	}
	ArrayDestroy(soundData)
}

array_remove( index )
{
	new sData[SOUND_DATA_BASE]
	ArrayGetArray(soundData, index, sData)
	ArrayDestroy(sData[SUB_INDEX])
	if ( index > 1 ) // join/exit keywords may not be removed
		ArrayDeleteItem(soundData, index)
	else
		sData[SUB_INDEX] = _:ArrayCreate(SOUND_DATA_SUB)
}

array_remove_inner( index , elem )
{
	new sData[SOUND_DATA_BASE]
	ArrayGetArray(soundData, index, sData)
	--sData[SOUND_AMOUNT]
	ArrayDeleteItem(sData[SUB_INDEX], elem)
	ArraySetArray(soundData, index, sData)
}

cfg_write_keyword( file , data[] )
{
	if ( data[ADMIN_LEVEL_BASE] )
	{
		new access_str[32]
		get_flags(data[ADMIN_LEVEL_BASE], access_str, 31)
		fputc(file, '@')
		fputs(file, access_str)
		fputc(file, '@')
	}
	fputs(file, data[KEYWORD])
	fputs(file, ";^t^t")
}

cfg_write_keysound( file , subdata[] )
{
	if ( subdata[ADMIN_LEVEL] )
	{
		new access_str[32]
		get_flags(subdata[ADMIN_LEVEL], access_str, 31)
		fputc(file, '@')
		fputs(file, access_str)
		fputc(file, '@')
	}
	fputs(file, subdata[SOUND_FILE])
	fputs(file, ";")
}

cfg_parse_access( str[] )
{
	new access_level
	if ( str[0] == '@' )
	{
		new second_at = contain(str[1], "@")
		if ( second_at != -1 )
		{
			new temp_access[32]
			copy(temp_access, second_at, str[1])
			strtolower(temp_access)
			access_level = read_flags(temp_access)
			copy(str, 127, str[second_at + 1 + 1])
		}else
		{
			access_level = SND_IMMUNITY
			copy(str, 127, str[1])
		}
	}
	
	return access_level
}

Float:cfg_get_duration( sound_file[] , type )
{
	switch ( type )
	{
		case SOUND_TYPE_WAV:
		{
			return cfg_get_wav_duration(sound_file)
		}
		case SOUND_TYPE_MP3:
		{
			return cfg_get_mp3_duration(sound_file)
		}
	}
	
	return 0.0
}

Float:cfg_get_wav_duration( wav_file[] )
{
	new file = fopen(wav_file, "rb")
	new dummy_input
	new i
	for ( i = 0; i < 24; ++i )
		dummy_input = fgetc(file)
	
	// 24th byte
	new hertz = fgetc(file)
	// 25th byte
	hertz += fgetc(file) * 256
	// 26th byte
	hertz += fgetc(file) * 256 * 256
	
	for ( i = 27; i < 34; ++i )
		dummy_input = fgetc(file)
	
	// 34th byte
	new bitrate = fgetc(file)
	
	// bytes for data length start right after ascii "data", so search for it
	// normally it is at 35 but also saw at 44, so just in case add bigger search area
	new data_found
	
	do
	{
		dummy_input = fgetc(file)
		if ( dummy_input == 'd' )
			data_found = 1
		else if ( dummy_input == 'a'
			&& data_found == 1 )
			data_found = 2
		else if ( dummy_input == 't'
			&& data_found == 2 )
			data_found = 3
		else if ( dummy_input == 'a'
			&& data_found == 3 )
			data_found = 4
		else
			data_found = 0
	}while ( dummy_input != -1 && data_found < 4 )
	
	if ( dummy_input == -1
		|| hertz <= 0
		|| bitrate <= 0
		|| data_found != 4 )
	{
		fclose(file)
		return 0.0
	}
	
	// 1st byte after data
	new data_length = fgetc(file)
	// 2nd byte after data
	data_length += fgetc(file) * 256
	// 3rd byte after data
	data_length += fgetc(file) * 256 * 256
	// 4th byte after data
	data_length += fgetc(file) * 256 * 256 * 256
	
	fclose(file)
	
	return float(data_length) / ( float(hertz * bitrate) / 8.0 )
}

enum
{
	MP3_MPEG_VERSION_BIT1 = 8,
	MP3_MPEG_VERSION_BIT2 = 16,
	MP3_LAYER_BIT1 = 2,
	MP3_LAYER_BIT2 = 4,
	MP3_PROTECT_BIT = 1,
	
	MP3_BITRATE_BIT1 = 16,
	MP3_BITRATE_BIT2 = 32,
	MP3_BITRATE_BIT3 = 64,
	MP3_BITRATE_BIT4 = 128,
	MP3_BITRATE_INVALID = 15,
	MP3_SAMPLERATE_BIT1 = 4,
	MP3_SAMPLERATE_BIT2 = 8,
	MP3_SAMPLERATE_INVALID = 3,
	MP3_PADDING_BIT = 2,
	MP3_PRIVATE_BIT = 1,
}

// bitrate info
new const bitrate_table[] = {
	//MPEG 2 & 2.5
	0, 32, 48, 56,  64,  80,  96, 112, 128, 144, 160, 176, 192, 224, 256, -1,	// Layer I
	0,  8, 16, 24,  32,  40,  48,  56,  64,  80,  96, 112, 128, 144, 160, -1,	// Layer II
	0,  8, 16, 24,  32,  40,  48,  56,  64,  80,  96, 112, 128, 144, 160, -1,	// Layer III
	//MPEG 1
	0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1,	// Layer I
	0, 32, 48, 56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, 384, -1,	// Layer II
	0, 32, 40, 48,  56,  64,  80,  96, 112, 128, 160, 192, 224, 256, 320, -1,	// Layer III
}

#if DEBUG_MODE == 1
// frequency info
new const samplingrate_table[] = {
	11025, 12000,  8000, 0,	// MPEG 2.5	// have not seen MPEG 2.5, so UNTESTED
	   -1,    -1,    -1, 0,	// reserved
	22050, 24000, 16000, 0,	// MPEG 2
	44100, 48000, 32000, 0	// MPEG 1
}
#endif

Float:cfg_get_mp3_duration( mp3_file[] )
{
	new file = fopen(mp3_file, "rb")
	new byte, found_header, file_pos
	new byte2
	
	new mpeg_version
	new layer
	new mp3_bitrate
	new mp3_samplerate
	new result = -1
	do
	{
		byte = fgetc(file)
		if ( byte == -1 )
			break
		
		++file_pos
		if ( byte != 255 )
			continue
		
		byte = fgetc(file)
		byte2 = fgetc(file)
		result = verify_header(byte, byte2, mpeg_version, layer, mp3_bitrate, mp3_samplerate)
		if ( result == -1 )
		{
			fseek(file, file_pos, SEEK_SET)
			++file_pos
			continue
		}else
			break
	}while ( !found_header && byte != -1 )
	
	fclose(file)
	
	if ( byte == -1 )
		return 0.0
	
	new mpeg_version_for_bitrate = 0
	if ( mpeg_version == 3 )
		mpeg_version_for_bitrate = 1
	new mp3_bitrate_kbps = bitrate_table[mpeg_version_for_bitrate * ( 3 * 16 ) + ( layer - 1 ) * 16 + mp3_bitrate]
	
#if DEBUG_MODE == 1
	log_amx("Sank Sounds >> DEBUG a fajlnal ^"%s^"", mp3_file)
	log_amx("Sank Sounds >> Data bytes = %i / %i", byte, byte2)
	log_amx("Sank Sounds >> Fejfajl pozicio = %i", file_pos)
	new mpeg_version_str[10]
	if ( mpeg_version == 0 )
		copy(mpeg_version_str, 9, "MPEG 2.5")
	else if ( mpeg_version == 2 )
		copy(mpeg_version_str, 9, "MPEG 2")
	else if ( mpeg_version == 3 )
		copy(mpeg_version_str, 9, "MPEG 1")
	log_amx("Sank Sounds >> MPEG verzio = %i / Formatum: %s", mpeg_version, mpeg_version_str)
	log_amx("Sank Sounds >> Layer = %i", layer)
	log_amx("Sank Sounds >> Bitrate = %iKbps (%i)", mp3_bitrate_kbps, mp3_bitrate)
	
	new mp3_samplerate_hz = samplingrate_table[mpeg_version * 4 + mp3_samplerate]
	
	log_amx("Sank Sounds >> Samplerate = %iHz (%i)", mp3_samplerate_hz, mp3_samplerate)
#endif
	new size_of_file = file_size(mp3_file, 0)
	
	if ( mp3_bitrate_kbps == 0 )
		return 0.0
	
	//song length...
	return float(size_of_file) / ( float(mp3_bitrate_kbps) * 1000.0 ) * 8.0
}

verify_header( header , header2 , &mpeg_version , &layer , &mp3_bitrate , &mp3_samplerate)
{
	// check if first 3 bits set
	if ( header & 0xe0 != 0xe0 )
		return -1
	
	layer = 4
		- ( header & MP3_LAYER_BIT1 ) / MP3_LAYER_BIT1
		+ ( header & MP3_LAYER_BIT2 ) / MP3_LAYER_BIT1
	
	if ( layer != 3 )
		return -1
	
	mp3_bitrate = ( header2 & MP3_BITRATE_BIT1 ) / MP3_BITRATE_BIT1
		+ ( header2 & MP3_BITRATE_BIT2 ) / MP3_BITRATE_BIT1
		+ ( header2 & MP3_BITRATE_BIT3 ) / MP3_BITRATE_BIT1
		+ ( header2 & MP3_BITRATE_BIT4 ) / MP3_BITRATE_BIT1
	
	if ( mp3_bitrate & MP3_BITRATE_INVALID == MP3_BITRATE_INVALID )
		return -1
	
	mp3_samplerate = ( header2 & MP3_SAMPLERATE_BIT1 ) / MP3_SAMPLERATE_BIT1
		+ ( header2 & MP3_SAMPLERATE_BIT2 ) / MP3_SAMPLERATE_BIT1
	
	if ( mp3_samplerate & MP3_SAMPLERATE_INVALID == MP3_SAMPLERATE_INVALID )
		return -1
	
	mpeg_version = ( header & MP3_MPEG_VERSION_BIT1 ) / MP3_MPEG_VERSION_BIT1
		+ ( header & MP3_MPEG_VERSION_BIT2 ) / MP3_MPEG_VERSION_BIT1
	
	return 1
}