sank sounds admini

Discutii despre tot ce nu se incadreaza in celelalte categorii.

Moderators: Moderatori ajutatori, Moderatori

Post Reply
Van DamMe ;x
Membru, skill 0
Membru, skill 0
Posts: 16
Joined: 23 Jan 2011, 19:12
Detinator Steam: Da
CS Status: Citesc forumul nordcs.com
Detinator server CS: cs.nordcs.com
Has thanked: 2 times
Contact:

14 Apr 2011, 20:19

salutare all am si eu o mica dilema cu sank_soungs
asi vrea sa il pun numie pentru admini

Code: Select all

/***************************************************************************
* This plugin reads keyword/wav/mp3 combinations from a configfile and when
* a player says one of the keywords, it will trigger HL to play that Wav/MP3
* file to all or dead/alive players. It allows reloading of the file without
* restarting the current level, as well as adding keyword/wav/mp3
* combinations from the console during gameplay. Also includes banning
* players from playing sounds.
*
* Credits:
*	- Luke Sankey				->	original author
#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 MAX_KEYWORDS	80				// Maximum number of keywords ( ATTENTION: 2 are reserved )
#define MAX_RANDOM	15				// Maximum number of sounds per keyword
#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
#define BUFFER_LEN	TOK_LENGTH * MAX_RANDOM

//#pragma dynamic 16384
#pragma dynamic 65536

#define ACCESS_ADMIN	ADMIN_LEVEL_A

#define PLUGIN_AUTHOR		"White Panther, Luke Sankey, HunteR"
#define PLUGIN_VERSION		"1.6.6"

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 kicked 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 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[128]

enum
{
	PARSE_SND_MAX,
	PARSE_SND_MAX_DUR,
	PARSE_SND_WARN,
	PARSE_SND_DELAY,
	PARSE_SND_MODE,
	PARSE_EXACT_MATCH,
	PARSE_ADMINS_ONLY,
	PARSE_DISPLAY_KEYWORDS,
	PARSE_KEYWORD
}

enum
{
	ERROR_NONE,
	ERROR_MAX_KEYWORDS,
	ERROR_STRING_LENGTH
}

enum
{
	FLAG_IGNORE_AMOUNT = 1
}

enum
{
	SOUND_TYPE_SPEECH,
	SOUND_TYPE_MP3,
	SOUND_TYPE_WAV,
	SOUND_TYPE_WAV_NOSUB
}

enum SOUND_DATA_BASE
{
	KEYWORD[TOK_LENGTH],
	ADMIN_LEVEL_BASE,
	SOUND_AMOUNT,
	FLAGS,
	PLAY_COUNT_KEY,
	
	KEY_SOUNDS[BUFFER_LEN],
	Float:DURATION[MAX_RANDOM],
	ADMIN_LEVEL[MAX_RANDOM],
	SOUND_TYPE[MAX_RANDOM],
	PLAY_COUNT[MAX_RANDOM],
	
	SOUND_DATA_BASE_END
}

new sound_data[MAX_KEYWORDS][SOUND_DATA_BASE]

public plugin_init( )
{
	register_plugin("Sank Sounds Plugin", PLUGIN_VERSION, PLUGIN_AUTHOR)
	register_cvar("sanksounds_version", PLUGIN_VERSION, FCVAR_SERVER)
	
	register_concmd("amx_sound_reset", "amx_sound_reset", ACCESS_ADMIN, " <user | all> : Resets sound quota for ^"user^", or everyone if ^"all^"")
	register_concmd("amx_sound_add", "amx_sound_add", ACCESS_ADMIN, " <keyword> <dir/sound> : Adds a Word/Sound combo to the sound list")
	register_clcmd("amx_sound_help", "amx_sound_help")
	register_concmd("amx_sound", "amx_sound", ACCESS_ADMIN, " :  Turns sounds on/off")
	register_concmd("amx_sound_play", "amx_sound_play", ACCESS_ADMIN, " <dir/sound> : Plays sound to all users")
	register_concmd("amx_sound_reload", "amx_sound_reload", ACCESS_ADMIN, " : Reloads config file. Filename is optional. If no filename, default is loaded")
	register_concmd("amx_sound_remove", "amx_sound_remove", ACCESS_ADMIN, " <keyword> <dir/sound> : Removes a Word/Sound combo from the sound list. Must use quotes")
	register_concmd("amx_sound_write", "amx_sound_write", ACCESS_ADMIN, " :  Writes current sound configuration to file")
	register_concmd("amx_sound_debug", "amx_sound_debug", ACCESS_ADMIN, "prints the whole Word/Sound combo list")
	register_concmd("amx_sound_ban", "amx_sound_ban", ACCESS_ADMIN, " <name or #userid>: Bans player from using sounds for current map")
	register_concmd("amx_sound_unban", "amx_sound_unban", ACCESS_ADMIN, " <name or #userid>: Unbans player from using sounds for current map")
	
	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()
}

public plugin_cfg( )
{
	get_cvar_string("mp_sank_sounds_motd_address", motd_sound_list_address, 127)
	
	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
	
	if ( sound_data[0][SOUND_AMOUNT] == 0 )
		return
	
	if ( Join_exit_SoundTime >= gametime )
		return
	
	new rand = random(sound_data[0][SOUND_AMOUNT])
	new playFile[TOK_LENGTH]
	copy(playFile, TOK_LENGTH, sound_data[0][KEY_SOUNDS][TOK_LENGTH * rand])
	
	if ( sound_data[0][ADMIN_LEVEL][rand] != 0
		&& !(get_user_flags(id) & sound_data[0][ADMIN_LEVEL][rand]) )
		return
	
	playsoundall(playFile, sound_data[0][SOUND_TYPE][rand])
	
	Join_exit_SoundTime = gametime + sound_data[0][DURATION][rand]
	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
	
	if ( sound_data[1][SOUND_AMOUNT] == 0 )
		return
	
	if ( Join_exit_SoundTime >= gametime )
		return
	
	new rand = random(sound_data[1][SOUND_AMOUNT])
	new playFile[TOK_LENGTH]
	copy(playFile, TOK_LENGTH, sound_data[1][KEY_SOUNDS][TOK_LENGTH * rand])
	
	if ( sound_data[1][ADMIN_LEVEL][rand] != 0
		&& !(get_user_flags(id) & sound_data[1][ADMIN_LEVEL][rand]) )
		return
		
	playsoundall(playFile, sound_data[1][SOUND_TYPE][rand])
	
	Join_exit_SoundTime = gametime + sound_data[1][DURATION][rand]
	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 >> Quota has been reseted for all players")
		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 >> Quota has been reseted for ^"%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 >>Invalid format")
		client_print(id, print_console, "Sank Sounds >>USAGE: amx_sound_add keyword <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, "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
	for( i = 0; i < MAX_KEYWORDS; ++i )
	{
		// If an empty string, then break this loop
		if ( strlen(sound_data[i][KEYWORD]) == 0 )
			break
		
		// If no match found, keep looping
		if ( !equal(Word, sound_data[i][KEYWORD], TOK_LENGTH) )
			continue
		
		// See if the Sound already exists
		for( j = 0; j < MAX_RANDOM; ++j )
		{
			// If an empty string, then break this loop
			if ( strlen(sound_data[i][KEY_SOUNDS][TOK_LENGTH * j]) == 0 )
				break

			// See if this is the same as the new Sound
			if ( equali(Sound, sound_data[i][KEY_SOUNDS][TOK_LENGTH * j], TOK_LENGTH) )
			{
				client_print(id, print_console, "Sank Sounds >> ^"%s; %s^" already exists", Word, Sound)
				
				return PLUGIN_HANDLED
			}
		}
		
		// If we reached the end, then there is no room
		if ( j >= MAX_RANDOM - 1 )
			client_print(id, print_console, "Sank Sounds >> No room for new Sound. Increase MAX_RANDOM and recompile")
		else
		{
			// Word exists, but Sound is new to the list, so add entry
			array_add_inner_element(i, j, Sound)
			
			client_print(id, print_console, "Sank Sounds >> ^"%s^" successfully added to ^"%s^"", Sound, Word)
		}
		
		return PLUGIN_HANDLED
	}
	
	// If we reached the end, then there is no room
	if ( i >= MAX_KEYWORDS )
		client_print(id, print_console, "Sank Sounds >> No room for new Word/Sound combo. Increase MAX_KEYWORDS and recompile")
	else
	{
		// Word/Sound combo is new to the list, so make a new entry
		array_add_element(i, Word)
		array_add_inner_element(i, j, Sound)
		
		client_print(id, print_console, "Sank Sounds >> ^"%s; %s^" successfully added", Word, Sound)
	}
	
	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 >> Plugin already enabled")
		else
		{
			bSoundsEnabled = 1
			console_print(id, "Sank Sounds >> Plugin enabled")
			client_print(0, print_chat, "Sank Sounds >> Plugin has been enabled")
			if ( Enable_Sound[0] )
			{
				new type = Enable_Sound[0] == '^"' ? SOUND_TYPE_SPEECH : ( Enable_Sound[strlen(Enable_Sound) - 1] == '3' ? SOUND_TYPE_MP3 : ( contain(Enable_Sound, "/") != -1 ? SOUND_TYPE_WAV : SOUND_TYPE_WAV_NOSUB) )
				playsoundall(Enable_Sound, type)
			}
		}
		
		return PLUGIN_HANDLED
	}else if ( equal(onoff, "off")
		|| equal(onoff, "0") )
	{
		if ( bSoundsEnabled == 0 )
			console_print(id, "Sank Sounds >> Plugin already disabled")
		else
		{
			bSoundsEnabled = 0
			console_print(id, "Sank Sounds >> Plugin disabled")
			client_print(0, print_chat, "Sank Sounds >> Plugin has been disabled")
			if ( Disable_Sound[0] )
			{
				new type = Disable_Sound[0] == '^"' ? SOUND_TYPE_SPEECH : ( Disable_Sound[strlen(Disable_Sound) - 1] == '3' ? SOUND_TYPE_MP3 : ( contain(Disable_Sound, "/") != -1 ? SOUND_TYPE_WAV : SOUND_TYPE_WAV_NOSUB) )
				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 >> Sound is invalid.")
		
		return PLUGIN_HANDLED
	}
	
	new type = arg[0] == '^"' ? SOUND_TYPE_SPEECH : ( arg[strlen(arg) - 1] == '3' ? SOUND_TYPE_MP3 : ( contain(arg, "/") != -1 ? SOUND_TYPE_WAV : SOUND_TYPE_WAV_NOSUB) )
	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
	for( new i = 0; i < MAX_KEYWORDS; ++i )
		array_clear_element(i)
	
	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 >> Invalid format")
		client_print(id, print_console, "Sank Sounds >> USAGE: amx_sound_remove keyword <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
	for( iCurWord = 0; iCurWord < MAX_KEYWORDS; ++iCurWord )
	{
		// If an empty string, then break this loop, we're at the end
		if ( strlen(sound_data[iCurWord][KEYWORD]) == 0 )
			break
		
		// Look for a Word match
		if ( !equali(Word, sound_data[iCurWord][KEYWORD], TOK_LENGTH) )
			continue
		
		// If no Sound was specified, then remove the whole Word's entry
		if ( strlen(Sound) == 0 )
		{
			// special check for join / exit keywords
			if ( iCurWord < 2 )
			{
				// safe join / exit data
				new temp_char = sound_data[iCurWord][KEYWORD][0]
				new temp_flag = sound_data[iCurWord][FLAGS]
				
				// Delete the last data
				array_clear_element(iCurWord)
				
				// restore data
				sound_data[iCurWord][KEYWORD][0] = temp_char
				sound_data[iCurWord][FLAGS] = temp_flag
				
				// We reached the end
				client_print(id, print_console, "Sank Sounds >> %s successfully cleared", Word)
				
				return PLUGIN_HANDLED
			}
			
			array_remove(iCurWord)
			
			client_print(id, print_console, "Sank Sounds >> %s successfully removed", Word)
			
			return PLUGIN_HANDLED
		}
			
		// Just remove the one Sound, if it exists
		for( jCurSound = 0; jCurSound < MAX_RANDOM; ++jCurSound )
		{
			// If an empty string, then break this loop, we're at the end
			if ( !strlen(sound_data[iCurWord][KEY_SOUNDS][TOK_LENGTH * jCurSound]) )
				break
			
			// Look for a Sound match
			if ( !equali(Sound, sound_data[iCurWord][KEY_SOUNDS][TOK_LENGTH * jCurSound], TOK_LENGTH) )
				continue
			
			if ( sound_data[iCurWord][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 successfully removed", Word)
			}else
			{
				array_remove_inner(iCurWord, jCurSound)
				
				client_print(id, print_console, "Sank Sounds >> %s successfully removed from %s", 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 not found", Sound)
		
		return PLUGIN_HANDLED
	}
	// We reached the end, and the Word didn't exist
	client_print(id, print_console, "Sank Sounds >> %s not found", 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) == 0 )
	{
		client_print(id, print_console, "Sank Sounds >> You must specify a filename")
		
		return PLUGIN_HANDLED
	}
	
	// disallow to use same filename as the default config_filename
	if ( equali(savefile, config_filename) )
	{
		client_print(id, print_console, "Sank Sounds >> Illegal write to default sound config file")
		client_print(id, print_console, "Sank Sounds >> Specify a different filename")
		
		return PLUGIN_HANDLED
	}
	
	/************ File should have the following format: **************
	# TimeStamp:		07:15:00 Monday January 15, 2001
	# File created by:	[SPU]Crazy_Chevy

	# Important parameters:
	SND_MAX;		20
	SND_MAX_DUR;		180.0
	SND_WARN;		17
	SND_JOIN;		misc/hi.wav
	SND_EXIT;		misc/comeagain.wav
	SND_DELAY;		0.0
	SND_MODE;		15
	EXACT_MATCH;		1
	ADMINS_ONLY;		0
	DISPLAY_KEYWORDS;	1

	# Word/Sound combinations:
	crap;			misc/awwcrap.Wav;misc/awwcrap2.wav
	woohoo;			misc/woohoo.wav
	@ha ha;			misc/haha.wav
	doh;			misc/doh.wav;misc/doh2.wav;@misc/doh3.wav

	******************************************************************/
	
	new TimeStamp[128], name[33], Text[BUFFER_LEN + TOK_LENGTH]
	new Textlen = BUFFER_LEN + TOK_LENGTH - 1
	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 >> Unable to read from ^"%s^" file", savefile)
		
		return PLUGIN_HANDLED
	}
	
	formatex(Text, Textlen, "# TimeStamp:^t^t%s^n", TimeStamp)
	fputs(file, Text)
	formatex(Text, Textlen, "# File created by:^t%s^n", name)
	fputs(file, Text)
	fputs(file, "^n")		// blank line
	fputs(file, "# Important parameters:^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 joinex_snd_buff[BUFFER_LEN]
	cfg_write_keysound(0, joinex_snd_buff, BUFFER_LEN - 1)
	formatex(Text, Textlen, "SND_JOIN;^t^t%s^n", joinex_snd_buff)
	fputs(file, Text)
	joinex_snd_buff[0] = 0
	cfg_write_keysound(1, joinex_snd_buff, BUFFER_LEN - 1)
	formatex(Text, Textlen, "SND_EXIT;^t^t%s^n", joinex_snd_buff)
	fputs(file, Text)
	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)
	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")
	
	for ( new i = 2; i < MAX_KEYWORDS; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		// See if we reached the end
		if ( strlen(sound_data[i][KEYWORD]) == 0 )
			break
		
		cfg_write_keyword(i, Text, Textlen)
		cfg_write_keysound(i, Text, Textlen)
		
		new text_len = strlen(Text)
		if ( text_len + 2 <= BUFFER_LEN )
		{
			Text[text_len] = '^n'	// add new line
			Text[text_len + 1] = 0
		}
		
		// Now write the formatted string to the file
		fputs(file, Text)
		
		// And loop for the next Sound
	}
	
	fclose(file)
	
	client_print(id, print_console, "Sank Sounds >> Configuration successfully written to %s", 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^n", SND_WARN, SND_MAX, SND_MAX_DUR)
	
	for( i = 0; i < MAX_RANDOM; ++i )
	{
		new tempstr[TOK_LENGTH]
		if ( strlen(sound_data[0][KEY_SOUNDS][TOK_LENGTH * i]) )
		{
			formatex(tempstr, TOK_LENGTH, "%s;", sound_data[0][KEY_SOUNDS][TOK_LENGTH * i])
			add(join_snd_buff, BUFFER_LEN, tempstr)
		}
		if ( strlen(sound_data[1][KEY_SOUNDS][TOK_LENGTH * i]) )
		{
			formatex(tempstr, TOK_LENGTH, "%s;", sound_data[1][KEY_SOUNDS][TOK_LENGTH * i])
			add(exit_snd_buff, BUFFER_LEN, tempstr)
		}
	}
	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^nEXACT_MATCH: %d", SND_DELAY, SND_MODE, EXACT_MATCH)
		client_print(id, print_console, "ADMINS_ONLY: %d^nDISPLAY_KEYWORDS: %d", ADMINS_ONLY, DISPLAY_KEYWORDS)
	}else
	{
		server_print("SND_JOIN: %s^n", join_snd_buff)
		server_print("SND_EXIT: %s^n", exit_snd_buff)
		server_print("SND_DELAY: %f^nSND_MODE: %d^nEXACT_MATCH: %d^n", SND_DELAY, SND_MODE, EXACT_MATCH)
		server_print("ADMINS_ONLY: %d^nDISPLAY_KEYWORDS: %d^n", ADMINS_ONLY, DISPLAY_KEYWORDS)
	}

	// Print out the matrix of sound data, so we got what we think we did
	for( i = 2; i < MAX_KEYWORDS; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		if ( strlen(sound_data[i][KEYWORD]) == 0 )
			break
		
		new access_level[32]
		get_flags(sound_data[i][ADMIN_LEVEL_BASE], access_level, 31)
		if ( id )
			client_print(id, print_console, "^n[%d] ^"%s^" with %d sound%s and level ^"%s^" (played: %d)", i - 2, sound_data[i][KEYWORD], sound_data[i][SOUND_AMOUNT], sound_data[i][SOUND_AMOUNT] > 1 ? "s" : "", access_level, sound_data[i][PLAY_COUNT_KEY])
		else
			server_print("^n[%d] ^"%s^" with %d sound%s and level ^"%s^" (played: %d)", i - 2, sound_data[i][KEYWORD], sound_data[i][SOUND_AMOUNT], sound_data[i][SOUND_AMOUNT] > 1 ? "s" : "", access_level, sound_data[i][PLAY_COUNT_KEY])
		for( j = 0; j < MAX_RANDOM; ++j )
		{
			if ( strlen(sound_data[i][KEY_SOUNDS][j * TOK_LENGTH]) == 0 )
				continue
			
			get_flags(sound_data[i][ADMIN_LEVEL][j], access_level, 31)
			if ( id )
				client_print(id, print_console, " ^"%s^" - time: %5.2f - admin level ^"%s^" (played: %d)", sound_data[i][KEY_SOUNDS][j * TOK_LENGTH], sound_data[i][DURATION][j], access_level, sound_data[i][PLAY_COUNT][j])
			else
				server_print(" ^"%s^" - time: %5.2f - admin level ^"%s^" (played: %d)", sound_data[i][KEY_SOUNDS][j * TOK_LENGTH], sound_data[i][DURATION][j], access_level, sound_data[i][PLAY_COUNT][j])
		}
	}
	
	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) & 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 >> Player ^"%s^" has been banned from using sounds", 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 >> Player ^"%s^" has been unbanned from using sounds", name)
	
	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:
	if ( !strlen(Speech) )
		return PLUGIN_CONTINUE
	
	if ( equal(Speech, "/sound", 6) )
	{
		if ( Speech[6] == 's' )
		{
			if ( Speech[7] == 'o'
				&& Speech[8] == 'n' )
			{
				SndOn[id] = 1
				client_print(id, print_chat, "Sank Sounds >> You will hear all sounds again")
			}else if ( Speech[7] == 'o'
				&& Speech[8] == 'f'
				&& Speech[9] == 'f'
				&& Speech[10] == 0 )
			{
				SndOn[id] = 0
				client_print(id, print_chat, "Sank Sounds >> I will stop playing sounds for you")
			}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
	// Check to see if what the player said is a trigger for a sound
	for ( new i = 2; i < MAX_KEYWORDS; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		// end of list reached
		if ( sound_data[i][KEYWORD][0] == 0 )
			break;
		
		if ( equali(Speech, sound_data[i][KEYWORD])
			|| ( EXACT_MATCH == 0
				&& containi(Speech, sound_data[i][KEYWORD]) != -1 ) )
		{
			// check for access
			if ( sound_data[i][ADMIN_LEVEL_BASE] == 0
				|| get_user_flags(id) & sound_data[i][ADMIN_LEVEL_BASE] )
				ListIndex = i
			
			break
		}
	}
	
	// check If player used NO sound trigger
	if ( ListIndex == -1 )
		return PLUGIN_CONTINUE
	
	new obey_duration_mode = get_pcvar_num(CVAR_obey_duration)
	new admin_flags = get_user_flags(id)
	new Float:gametime = get_gametime()
	if ( gametime > NextSoundTime + SND_DELAY			// 1.  check for sound overlapping + delay time
		|| ( admin_flags & ADMIN_RCON				// 2.  check if super admin
			&& !(obey_duration_mode & 4) )			// 2b. check if super admin have to obey duration
		|| ( admin_flags & ACCESS_ADMIN				// 3.  check if admin
			&& !(obey_duration_mode & 2) )			// 3b. check if admin have to obey duration
		|| ( !(obey_duration_mode & 1)				// 4.  check if overlapping is allowed
			&& gametime > LastSoundTime + SND_DELAY ) )	// 4b. or for delay time
	{
		// check if player is allowed to play sounds depending on config
		new alive = is_user_alive(id)
		if ( SND_MODE & ( alive + 1 )
			&& !QuotaExceeded(id) )		// If the user has not exceeded their quota, then play a Sound
		{
			new rand = random(sound_data[ListIndex][SOUND_AMOUNT])
			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(sound_data[ListIndex][SOUND_AMOUNT])
				// If for some reason we never find a file
				//  then default to the first Sound entry
				if ( !timeout )
					rand = 0
				
				// check if sound has access defined, if so only allow admins to use it
				if ( sound_data[ListIndex][ADMIN_LEVEL][rand] == 0
					|| ( get_user_flags(id) & sound_data[ListIndex][ADMIN_LEVEL][rand] ) )
					copy(playFile, TOK_LENGTH, sound_data[ListIndex][KEY_SOUNDS][rand * TOK_LENGTH])
			}
			
			if ( playFile[0] )
			{
				NextSoundTime = gametime + sound_data[ListIndex][DURATION][rand]
				
				// Increment their playsound count
				++SndCount[id]
				SndLenghtCount[id] += sound_data[ListIndex][DURATION][rand]
				
				// increment counter
				++sound_data[ListIndex][PLAY_COUNT_KEY]
				++sound_data[ListIndex][PLAY_COUNT][rand]
				
				playsoundall(playFile, sound_data[ListIndex][SOUND_TYPE][rand], SND_MODE & 16, alive)
				
				LastSoundTime = gametime
			}
		}
	}else if ( gametime <= NextSoundTime + SND_DELAY
		&& obey_duration_mode != 0 )
		client_print(id, print_chat, "Sank Sounds >> Sound is still playing ( wait %3.1f seconds )", NextSoundTime + SND_DELAY - gametime)
	else
		client_print(id, print_chat, "Sank Sounds >> Do not use sounds too often ( wait %3.1f seconds )", 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 >> Cannot find ^"%s^" file", 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 + TOK_LENGTH ) - 1
	new strLineBuf[BUFFER_LEN + TOK_LENGTH]
		
	new error_code = ERROR_NONE
	new parse_option = PARSE_KEYWORD
	new temp_str[128]
	new check_for_semi
	new position
	
	new file = fopen(loadfile, "r")
	if ( !file )
	{
		log_amx("Sank Sounds >> Unable to read from ^"%s^" file", 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
		
		if ( ListIndex >= MAX_KEYWORDS )
		{
			log_amx("Sank Sounds >> Sound list truncated. Increase MAX_KEYWORDS. Stopped parsing file ^"%s^"^n", loadfile)
			
			break
		}
		
		error_code = ERROR_NONE
		position = 0
		for( i = 0; i < MAX_RANDOM; ++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, "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
						&& sound_data[ListIndex][SOUND_AMOUNT] == 0
						&& !(sound_data[ListIndex][FLAGS] & FLAG_IGNORE_AMOUNT) )	// check if allowed to ignore amount of sounds ( eg: SND_JOIN / SND_EXIT )
						log_amx("Sank Sounds >> Found keyword without any valid sound. Skipping this keyword: ^"%s^"", sound_data[ListIndex][KEYWORD])
					else
						++ListIndex
					
					if ( ListIndex >= MAX_KEYWORDS )
					{
						error_code = ERROR_MAX_KEYWORDS
						
						break
					}
					
					new result = array_add_element(ListIndex, temp_str)
					if ( result == -2 )
						tmpIndex = result
					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_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 = 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, so clear that array entry
							if ( tmpIndex != -1 )
								array_clear_inner_element(tmpIndex, i - 1)
							else
								array_clear_inner_element(ListIndex, i - 1)
							
							continue
						}
					}
				}
			}
		}
		
		// Error occured so skip Word/Sound Combo
		if ( error_code == ERROR_MAX_KEYWORDS )
		{
			log_amx("Sank Sounds >> Sound list truncated. Increase MAX_KEYWORDS. Stopped parsing file ^"%s^"^n", loadfile)
			
			break
		}
		
		if ( error_code == ERROR_STRING_LENGTH )
		{
			log_amx("Sank Sounds >> Skipping this word/sound combo. Word or Sound is too long: ^"%s^". Length is %i but max is %i (change name/remove spaces in config or increase TOK_LENGTH)", 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 >> Sound list partially truncated. Increase MAX_RANDOM. Continuing to parse file ^"%s^"^n", loadfile)
		}
	}
	
	fclose(file)
	
	if ( ListIndex != -1 )
	{
		if ( sound_data[ListIndex][SOUND_AMOUNT] == 0
			&& !(sound_data[ListIndex][FLAGS] & FLAG_IGNORE_AMOUNT) )	// check if allowed to ignore amount of sounds ( eg: SND_JOIN / SND_EXIT )
		{
			log_amx("Sank Sounds >> Found keyword without any valid sound. Skipping this keyword: ^"%s^"", sound_data[ListIndex][KEYWORD])
			sound_data[ListIndex][KEYWORD][0] = 0
			--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 )
		sort_HeapSort(ListIndex - 1)	// -2 cause first two are reserved for join/exit sounds
#endif
}

//////////////////////////////////////////////////////////////////////////////
// Returns 0 if the user is allowed to say things
// Returns 1 and mutes the user if the quota has been exceeded.
//////////////////////////////////////////////////////////////////////////////
QuotaExceeded( id )
{
	// check if is admin
	new admin_check = ( get_user_flags(id) & ACCESS_ADMIN )
	
	if ( ADMINS_ONLY && !admin_check )
		return 1
	
	// If the sound limitation is disabled, then return happily.
	if ( admin_check )
		return 0
	
	if ( SND_MAX != 0 )
	{
		if ( SndCount[id] >= SND_MAX )
		{
			if ( SndCount[id] - 3 < SND_MAX )
			{
				client_print(id, print_chat, "Sank Sounds >> You were warned, you are muted")
				
				// 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
		}else if ( SndCount[id] >= SND_WARN )
			client_print(id, print_chat, "Sank Sounds >> You have %d left before you get muted", SND_MAX - SndCount[id])
	}
	
	if ( SND_MAX_DUR != 0.0
		&& SndLenghtCount[id] > SND_MAX_DUR )
		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 cannot be negative. Setting to value: 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 cannot be negative. Setting to value: 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 cannot be negative. Setting to value: 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 cannot be higher than SND_MAX")
		else if ( SND_WARN <= 0 )
			log_amx("Sank Sounds >> SND_WARN cannot be set to zero")
		
		if ( SND_MAX > 3 )
			SND_WARN = SND_MAX - 3
		else
			SND_WARN = SND_MAX - 1
		
		log_amx("Sank Sounds >> SND_WARN set to default value: %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_NOSUB )
			client_cmd(i, "play ^"%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[64] = "say < keyword >: plays A sound. keYwords are listed Below:"
	if ( strlen(motd_sound_list_address) > 3 )	// make sure at least you have something like: a.b ( http://a.b )
	{
		copy(motd_buffer, 127, 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
	for ( i = 2; i < MAX_KEYWORDS && skip_for_loop == 0; ++i )	// first 2 elements are reserved for Join / Exit sounds
	{
		// If an invalid string, then break this loop
		if ( strlen(sound_data[i][KEYWORD]) == 0
			|| strlen(sound_data[i][KEYWORD]) > TOK_LENGTH )
			break
		
		// check if player can see admin sounds
		++j
		new found_stricted = 0
		if ( sound_data[i][ADMIN_LEVEL_BASE] == 0
			|| get_user_flags(id) & sound_data[i][ADMIN_LEVEL_BASE] )
		{
			if ( motd_msg )
				ilen += format(motd_buffer[ilen], 2047 - ilen, "%s", sound_data[i][KEYWORD])
			else
				add(text, 255, sound_data[i][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
// 4 functions for array sort ( by Bailopan ) ( customized to fit plugin )
sort_HeapSort( ListIndex )
{
	new i
	new aSize = ( ListIndex / 2 ) - 1
	for ( i = aSize; i >= 0; --i )
		sort_SiftDown(i, ListIndex - 1)
	
	for ( i = ListIndex - 1; i >= 1; --i )
	{
		array_switch_elements(0, i)
		sort_SiftDown(0, i - 1)
	}
}

sort_compare( elem1 , elem2 )
{
	// skip first 2 elements ( join / exit )
	elem1 += 2
	elem2 += 2
	
	new i = 0
	for ( i = 0; i < TOK_LENGTH; ++i )
	{
		if ( sound_data[elem1][KEYWORD][i] != sound_data[elem2][KEYWORD][i] )
		{
			if ( sound_data[elem1][KEYWORD][i] > sound_data[elem2][KEYWORD][i] )
				return 1
			
			return -1
		}
	}
	
	return 0
}

sort_SiftDown( root , bottom )
{
	new done, child
	while ( ( root * 2 <= bottom ) && !done )
	{
		if ( root * 2 == bottom )
			child = root * 2
		else if ( sort_compare(root * 2, root * 2 + 1) > 0 )
			child = root * 2
		else
			child = root * 2 + 1
		
		if ( sort_compare(root, child) < 0 )
		{
			array_switch_elements(root, child)
			root = child
		}else
			done = 1
	}
}

array_switch_elements( element_one , element_two )
{
	// skip first 2 elements ( join / exit )
	element_one += 2
	element_two += 2
	
	new i
	new temp_sounds[BUFFER_LEN]
	new temp_keyword[TOK_LENGTH]
	new temp_int, Float:temp_float, temp_access, temp_access_base, temp_type, temp_flags, temp_play_count
	
	copy(temp_keyword, TOK_LENGTH, sound_data[element_one][KEYWORD])
	for ( i = 0; i < BUFFER_LEN; ++i )
		temp_sounds[i] = sound_data[element_one][KEY_SOUNDS][i]
	temp_int = sound_data[element_one][SOUND_AMOUNT]
	temp_access_base = sound_data[element_one][ADMIN_LEVEL_BASE]
	temp_flags = sound_data[element_one][FLAGS]
	temp_play_count = sound_data[element_one][PLAY_COUNT_KEY]
	
	copy(sound_data[element_one][KEYWORD], TOK_LENGTH, sound_data[element_two][KEYWORD])
	for ( i = 0; i < BUFFER_LEN; ++i )
		sound_data[element_one][KEY_SOUNDS][i] = sound_data[element_two][KEY_SOUNDS][i]
	sound_data[element_one][SOUND_AMOUNT] = sound_data[element_two][SOUND_AMOUNT]
	sound_data[element_one][ADMIN_LEVEL_BASE] = sound_data[element_two][ADMIN_LEVEL_BASE]
	sound_data[element_one][FLAGS] = sound_data[element_two][FLAGS]
	sound_data[element_one][PLAY_COUNT_KEY] = sound_data[element_two][PLAY_COUNT_KEY]
	
	copy(sound_data[element_two][KEYWORD], TOK_LENGTH, temp_keyword)
	for ( i = 0; i < BUFFER_LEN; ++i )
		sound_data[element_two][KEY_SOUNDS][i] = temp_sounds[i]
	sound_data[element_two][SOUND_AMOUNT] = temp_int
	sound_data[element_two][ADMIN_LEVEL_BASE] = temp_access_base
	sound_data[element_two][FLAGS] = temp_flags
	sound_data[element_two][PLAY_COUNT_KEY] = temp_play_count
	
	for ( i = 0; i < MAX_RANDOM; ++i )
	{
		temp_float = sound_data[element_one][DURATION][i]
		sound_data[element_one][DURATION][i] = _:sound_data[element_two][DURATION][i]
		sound_data[element_two][DURATION][i] = _:temp_float
		
		temp_access = sound_data[element_one][ADMIN_LEVEL][i]
		sound_data[element_one][ADMIN_LEVEL][i] = sound_data[element_two][ADMIN_LEVEL][i]
		sound_data[element_two][ADMIN_LEVEL][i] = temp_access
		
		temp_type = sound_data[element_one][SOUND_TYPE][i]
		sound_data[element_one][SOUND_TYPE][i] = sound_data[element_two][SOUND_TYPE][i]
		sound_data[element_two][SOUND_TYPE][i] = temp_type
		
		temp_play_count = sound_data[element_one][PLAY_COUNT][i]
		sound_data[element_one][PLAY_COUNT][i] = sound_data[element_two][PLAY_COUNT][i]
		sound_data[element_two][PLAY_COUNT][i] = temp_play_count
	}
}
#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
			}
		}
	}
	
	if ( join_check > 0
		|| exit_check > 0 )
		sound_data[num][FLAGS] |= FLAG_IGNORE_AMOUNT
	sound_data[num][ADMIN_LEVEL_BASE] = cfg_parse_access(keyword)
	copy(sound_data[num][KEYWORD], TOK_LENGTH, keyword)
	sound_data[num][PLAY_COUNT_KEY] = 0
	
	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 )
{
	sound_data[num][ADMIN_LEVEL][elem] = cfg_parse_access(soundfile)
	sound_data[num][SOUND_TYPE][elem] = soundfile[0] == '^"' ? SOUND_TYPE_SPEECH : ( soundfile[strlen(soundfile) - 1] == '3' ? SOUND_TYPE_MP3 : ( contain(soundfile, "/") != -1 ? SOUND_TYPE_WAV : SOUND_TYPE_WAV_NOSUB ) )
	sound_data[num][PLAY_COUNT][elem] = 0
	
	// check if not speech sounds
	if ( soundfile[0] != '^"' )
	{
		new sound_file_name[TOK_LENGTH + 1 + 10]
		new is_mp3 = ( containi(soundfile, ".mp") != -1 )
		if ( !is_mp3 )
		{	// ".mp3" in not in the string
			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 >> Trying to load a file that dont exist. Skipping this file: ^"%s^"", sound_file_name)
				
				return -1
			}
			
			sound_data[num][DURATION][elem] = _:cfg_get_duration(sound_file_name, is_mp3 ? SOUND_TYPE_MP3 : SOUND_TYPE_WAV )
			
			if ( sound_data[num][DURATION][elem] <= 0.0 )
			{
				log_amx("Sank Sounds >> Sound duration is not valid. File is damaged. Skipping this file: ^"%s^"", sound_file_name)
				
				return -1
			}
		}
		
		if ( allow_global_precache
			&& precache_sounds == 1
			&& allowed_to_precache )
		{
			if ( is_mp3 )
				//precache_generic(soundfile)
				engfunc(EngFunc_PrecacheGeneric, soundfile)
			else
				//precache_sound(soundfile)
				engfunc(EngFunc_PrecacheSound, soundfile)
		}
	}
	
	copy(sound_data[num][KEY_SOUNDS][TOK_LENGTH * elem], TOK_LENGTH, soundfile)
	++sound_data[num][SOUND_AMOUNT]
	
	return 1
}

array_clear_element( index )
{
	for ( new i = 0; i < TOK_LENGTH; ++i )
		sound_data[index][KEYWORD][i] = 0
	sound_data[index][SOUND_AMOUNT] = 0
	sound_data[index][ADMIN_LEVEL_BASE] = 0
	sound_data[index][FLAGS] = 0
	sound_data[index][PLAY_COUNT_KEY] = 0
	
	for ( new i = 0; i < MAX_RANDOM; ++i )
		array_clear_inner_element(index, i)
}

array_clear_inner_element( index , elem )
{
	for ( new i = 0; i < TOK_LENGTH; ++i )
		sound_data[index][KEY_SOUNDS][TOK_LENGTH * elem + i] = 0
	sound_data[index][DURATION][elem] = _:0.0
	sound_data[index][ADMIN_LEVEL][elem] = 0
	sound_data[index][SOUND_TYPE][elem] = 0
	sound_data[index][PLAY_COUNT][elem] = 0
}

array_copy_element( dest , source )
{
	copy(sound_data[dest][KEYWORD], TOK_LENGTH, sound_data[source][KEYWORD])
	sound_data[dest][SOUND_AMOUNT] = sound_data[source][SOUND_AMOUNT]
	sound_data[dest][ADMIN_LEVEL_BASE] = sound_data[source][ADMIN_LEVEL_BASE]
	sound_data[dest][FLAGS] = sound_data[source][FLAGS]
	sound_data[dest][PLAY_COUNT_KEY] = sound_data[source][PLAY_COUNT_KEY]
	
	for ( new i = 0; i < MAX_RANDOM; ++i )
		array_copy_inner_elements(dest, i, source, i)
}

array_copy_inner_elements( array1 , elem1 , array2 , elem2 )
{
	copy(sound_data[array1][KEY_SOUNDS][TOK_LENGTH * elem1], TOK_LENGTH, sound_data[array2][KEY_SOUNDS][TOK_LENGTH * elem2])
	sound_data[array1][DURATION][elem1] = _:sound_data[array2][DURATION][elem2]
	sound_data[array1][ADMIN_LEVEL][elem1] = sound_data[array2][ADMIN_LEVEL][elem2]
	sound_data[array1][SOUND_TYPE][elem1] = sound_data[array2][SOUND_TYPE][elem2]
	sound_data[array1][PLAY_COUNT][elem1] = sound_data[array2][PLAY_COUNT][elem2]
}

array_remove( index )
{
	// Keep looping array, copying the next into the current
	for ( ; index < MAX_KEYWORDS; ++index )
	{
		// We are at last List element or there is no succesor
		// so clear it cause we want to remove one element anyway
		if ( index == MAX_KEYWORDS - 1
			|| sound_data[index + 1][KEYWORD][0] == 0 )
		{
			// Delete data
			array_clear_element(index)
			
			// We reached the end
			return
		}
		
		// Copy the next data over the current
		array_copy_element(index, index + 1)
	}
}

array_remove_inner( index , elem )
{
	// we are removing an element, so decrease counter
	--sound_data[index][SOUND_AMOUNT]
	
	for( ; elem < MAX_RANDOM; ++elem )
	{
		// If we're about to copy data that doesn't exist,
		// then just erase the last entry instead of copying
		if ( elem == MAX_RANDOM - 1
			|| sound_data[index][KEY_SOUNDS][TOK_LENGTH * (elem + 1)] == 0 )
		{
			// Delete Sound
			array_clear_inner_element(index, elem)
			
			// We reached the end
			return
		}
		
		// else
		// Copy the next data over the current
		array_copy_inner_elements(index, elem, index, elem + 1)
	}
}

cfg_write_keyword( index , Text[] , Textlen )
{
	Text[0] = 0
	
	if ( sound_data[index][ADMIN_LEVEL_BASE] )
	{
		new access_str[32]
		get_flags(sound_data[index][ADMIN_LEVEL_BASE], access_str, 31)
		formatex(Text, Textlen, "@%s@%s;^t^t", access_str, sound_data[index][KEYWORD])
	}else
		formatex(Text, Textlen, "%s;^t^t", sound_data[index][KEYWORD])
	
}

cfg_write_keysound( index , Text[] , Textlen )
{
	new access_str[32]
	for ( new j = 0; j < MAX_RANDOM && strlen(sound_data[index][KEY_SOUNDS][TOK_LENGTH * j]); ++j )
	{
		if ( sound_data[index][ADMIN_LEVEL][j] )
		{
			get_flags(sound_data[index][ADMIN_LEVEL][j], access_str, 31)
			format(Text, Textlen, "%s@%s@%s;", Text, access_str, sound_data[index][KEY_SOUNDS][TOK_LENGTH * j])
		}else
			format(Text, Textlen, "%s%s;", Text, sound_data[index][KEY_SOUNDS][TOK_LENGTH * j])
	}
}

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 = ACCESS_ADMIN
			copy(str, 127, str[1])
		}
	}
	
	return access_level
}

Float:cfg_get_duration( sound_file[] , type )
{
	switch ( type )
	{
		case SOUND_TYPE_WAV, SOUND_TYPE_WAV_NOSUB:
		{
			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
		
		/*++file_pos
		if ( ( byte / 16 ) < 14
			|| byte == 255 )
			continue
		
		//if ( fgetc(file) > 80 )
		//if ( fgetc(file) > 0 )
		//if ( fgetc(file) > 40 )
		if ( fgetc(file) > 16 )
		{
			// header starts with hex: FF YY XX
			// YY must be YY modulo 16 = 15, but NOT equal 255 (mostly it is FB or F3)
			fseek(file, file_pos, SEEK_SET)
			found_header = 1
		}else
			++file_pos*/
	}while ( !found_header && byte != -1 )
	
	fclose(file)
	
	if ( byte == -1 )
		return 0.0
	
	//if ( mp3_bitrate == 2 )
	//	log_amx("Sank Sounds >> ^"%s^" has a samplerate of %iHz. This is not supported by Half Life 1 Engine", mp3_file, samplingrate_table[mpeg_version * 4 + mp3_samplerate])
	
	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 for file ^"%s^"", mp3_file)
	log_amx("Sank Sounds >> Data bytes = %i / %i", byte, byte2)
	log_amx("Sank Sounds >> Header position = %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 version = %i / Format: %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)
	
	//mp3_samplerate = samplingrate_table[mpeg_version * 3 + ( byte % 16 ) / 4]
	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
}

/*
* plugin_sank_sounds.sma
* Author: Luke Sankey
* Date: March 21, 2001 - Original hard-coded version
* Date: July 2, 2001   - Rewrote to be text file configurable
* Date: November 18, 2001 - Added admin_sound_play command, new variables
*       SND_DELAY, SND_SPLIT and EXACT_MATCH, as well as the ability to
*       have admin-only sounds, like the original version had.
* Date: March 30, 2002 - Now ignores speech of length zero.
* Date: May 30, 2002 - Updated for use with new playerinfo function
* Date: November 12, 2002 - Moved snd-list.cfg file to new location, and
*       made it all lower-case.  Sorry, linux guys, if it confuses you.
*       Added some new ideas from Bill Bateman:
*       1.) added SND_PUNISH and changed SND_KICK to SND_MAX
*       2.) ability to either speak or play sounds
*
* Last Updated: May 12, 2003
*
*
*
* HunteR's modifications:
*	- Players no longer kicked, they are "muted" (no longer able to play sounds)
*	- All sounds are now "spoken" (using the speak command)
*	- As a result, all "\" must become "/"
*	- Ability to reset a player's sound count mid-game
*
* My most deepest thanks goes to William Bateman (aka HunteR)
*  http://thepit.shacknet.nu
*  [email protected]
* For he was the one who got me motivated once again to write this plugin
* since I don't run a server anymore. And besides that, he helped write
* parts of it.
*
* I hope you enjoy this new functionality on the old plugin_sank_sounds
*/
cine ma poate ajuta sa il pun numai pentru admin
connect Cs.NordcsCs.Com
Image
site/forum
Cautam admini contatc Y!M: [email protected]
Post Reply

Return to “Discutii generale”

  • Information