/***************************************************************************
 * plugin_sank_sounds.sma
 * Author: Luke Sankey
 * March 21, 2001 - Original hard-coded version released.
 * July 2, 2001 - Rewrote to be text file configurable
 * 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.
 * March 30, 2002 - Fixed zero-length-speech bug.
 * May 30, 2002 - Updated for use with new playerinfo function (beta only)
 * November 15, 2002 - Moved snd-list.cfg file to new location, and made it all
 *  lower-case.  Sorry, linux guys, if it confuses you.  Fixed admin_sound_add
 *  bug when trying to change SND_* parameters. Added some ideas from Bill 
 *  Bateman:
 *  1.) added SND_PUNISH and changed SND_KICK to SND_MAX
 *  2.) ability to either speak or play sounds
 *
 * This plugin will read from a text file keyword/wav file combinations
 * and when a player says one of the keywords, it will trigger HL to play
 * that wav file to all players. It allows reloading of the file without
 * restarting the current level, as well as adding keyword/wav combinations
 * from the console during gameplay.
 *
 * My most deepest thanks goes to William Bateman (aka HunteR)
 *  http://thepit.shacknet.nu
 *  huntercc@hotmail.com
 * 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.
 *
 * For technical assistance with this plugin, please see the README.TXT file
 * that was bundled in the zip file.  If it fails you, email Bill Bateman or
 * me.
 *
 * I hope you enjoy this new functionality on the old plugin_sank_sounds!
 *
 * Luke Sankey
 * sank@spu.edu
 ***************************************************************************
 * edit by Sir D.:
 * - new override function for executing punish commands
 * - max keywords 200
 */
 
 
// Functions included in this plugin
//  admin_sound_add "<keyword>;<dir\wav>"
//  admin_sound_help
//  admin_sound_off
//  admin_sound_on
//  admin_sound_play <dir\wav>
//  admin_sound_reload [filename]
//  admin_sound_remove "<keyword>;[dir\wav]"
//  admin_sound_write <filename>
 
#include <core>
#include <console>
#include <string>
#include <admin>
#include <adminlib>
 
#define ACCESS_CONSOLE	131072
 
#define ACCESS_SOUND 512+64 // Access level for advanced sound commands.
#define ACCESS_ADMINSOUNDS	512+64 // Access level for playing admin sounds
 
new FILENAME[MAX_DATA_LENGTH] = "addons/adminmod/config/snd-list.cfg";	// Name of file to parse.
new punishfile[] = "addons/adminmod/config/sounds_punish.cfg"; //execute punish command
#define MAX_KEYWORDS 200 // Maximum number of keywords
#define MAX_RANDOM 10	// Maximum number of wavs per keyword
#define TOK_LENGTH 50	// Maximum length of keyword and wav file strings
#define NUM_PER_LINE 4	// Number of words per line from admin_sound_help
 
 
#define DEBUG 0
 
 
/****************************************************************************/
/****************************************************************************/
/************** DO NOT MODIFY CONTENTS BELOW THIS LINE !!! ******************/
/************** DO NOT MODIFY CONTENTS BELOW THIS LINE !!! ******************/
/************** DO NOT MODIFY CONTENTS BELOW THIS LINE !!! ******************/
/****************************************************************************/
/****************************************************************************/
 
 
new STRING_VERSION[MAX_DATA_LENGTH] = "2.50.50_sd";
 
/****************************************************************************/
/* Holds the number telling how many sounds a player has played             */
new SndCount[MAX_PLAYERS] = {0,...};
/* The number at which a player will get warned for playing too many sounds */
new SND_WARN = 0;
/* The number at which a player will get kicked for playing too many sounds */
new SND_MAX = 0;
/* The wav to play when a person joins the game                             */
new SND_JOIN[TOK_LENGTH] = "";
/* The wav to play when a person exits the game                             */
new SND_EXIT[TOK_LENGTH] = "";
/* The command to execute when a person exceeds the quota                   */
new SND_PUNISH[TOK_LENGTH] = "";
/* Minimum delay between sounds                                             */
new SND_DELAY = 0;
/* Determines if sounds play to all, or isolate dead and alive              */
new SND_SPLIT = 0;
/* Determines if plugin triggers on exact match, or partial speech match    */
new EXACT_MATCH = 1;
/****************************************************************************/
 
 
/****************************************************************************/
/* First column is the indentifier of the Word-Wav Combination              */
/* Second column is the token identifier                                    */
/* Third column is the token (string)                                       */
/* Empty tokens are delimited by empty strings                              */
/****************************************************************************/
/*
[0] [TOK_LENGTH*0] ["crap"]
[0] [TOK_LENGTH*1] ["misc/awwcrap.wav"]
[0] [TOK_LENGTH*2] ["misc/awwcrap2.wav"]
[0] [TOK_LENGTH*3] [""]
 
[1] [TOK_LENGTH*0] ["woohoo"]
[1] [TOK_LENGTH*1] ["misc/woohoo.wav"]
[1] [TOK_LENGTH*2] [""]
 
[2] [TOK_LENGTH*0] ["ha ha"]
[2] [TOK_LENGTH*1] ["misc/haha.wav"]
[2] [TOK_LENGTH*2] [""]
 
[3] [TOK_LENGTH*0] ["doh"]
[3] [TOK_LENGTH*1] ["misc/doh.wav"]
[3] [TOK_LENGTH*2] ["misc/doh2.wav"]
[3] [TOK_LENGTH*3] ["misc/doh3.wav"]
[3] [TOK_LENGTH*4] ["misc/doh4.wav"]
[3] [TOK_LENGTH*5] [""]
 
[4] [TOK_LENGTH*0] [""]
...
*/
new WordWavCombo[MAX_KEYWORDS][TOK_LENGTH*(MAX_RANDOM+1)];
/****************************************************************************/
 
 
new LastSoundTime = 0; // Very limited spam protection
new bSoundsEnabled = 1;	// admin_sound_on and admin_sound_off
 
stock playsoundall(sound[], IfDead = -1);
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
 
new g_User[MAX_PLAYERS];
 
 
public plugin_init()
{
	plugin_registerinfo("Sank Sounds Plugin", "Responds to certain chat messages by playing a sound.", STRING_VERSION);
 
	plugin_registercmd("say", "HandleSay", ACCESS_ALL);
	plugin_registercmd("specmode", "HandleSpecMode", ACCESS_ALL);
	plugin_registercmd("admin_sound_add", "admin_sound_add", ACCESS_SOUND, "admin_sound_add ^"<keyword>;<dir\wav>^" : Adds a word/wav combo to the sound list. Must use quotes.");
	plugin_registercmd("admin_sound_help", "admin_sound_help", ACCESS_ALL, "admin_sound_help: Lists all sounds and commands");
	plugin_registercmd("admin_sound_off", "admin_sound_off", ACCESS_SOUND, "admin_sound_off:  Turns off sounds.");
	plugin_registercmd("admin_sound_on", "admin_sound_on", ACCESS_SOUND, "admin_sound_on:  Turns on sounds.");
	plugin_registercmd("admin_sound_play", "admin_sound_play", ACCESS_SOUND, "admin_sound_play <dir\wav> : Plays sound to all users.");
	plugin_registercmd("admin_sound_reload", "admin_sound_reload", ACCESS_SOUND, "admin_sound_reload: Reloads config file. Filename is optional");
	plugin_registercmd("admin_sound_remove", "admin_sound_remove", ACCESS_SOUND, "admin_sound_remove ^"<keyword>;[dir\wav]^" : Removes a word/wav combo from the sound list. Must use quotes.");
	plugin_registercmd("admin_sound_write", "admin_sound_write", ACCESS_SOUND, "admin_sound_write:  Writes current sound configuration to file.");
	plugin_registercmd("snd_punish", "snd_punish", ACCESS_CONSOLE);
 
#if DEBUG
	plugin_registercmd("admin_sound_debug", "print_matrix", ACCESS_SOUND, "admin_sound_debug");
#endif
 
	parse_sound_file();
 
	return PLUGIN_CONTINUE;
}
 
public plugin_connect(HLUserName, HLIP, UserIndex) 
{
	if (UserIndex >= 1 && UserIndex <= MAX_PLAYERS){
		SndCount[UserIndex] = 0;
		g_User[UserIndex]=0;
	}
	return PLUGIN_CONTINUE;
}
 
public plugin_disconnect(HLUserName, UserIndex) 
{
	if (UserIndex >= 1 && UserIndex <= MAX_PLAYERS){
		SndCount[UserIndex] = 0;
		g_User[UserIndex]=0;
	}
 
	playsoundall(SND_EXIT);
 
	return PLUGIN_CONTINUE;
}
 
 
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
 
 
//////////////////////////////////////////////////////////////////////////////
// Adds a Word/Wav 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 .wav file at a time with this command.
//
// Usage: admin_sound_add "<keyword>;<dir\wav>"
// Usage: admin_sound_add "<setting>;<value>"
//////////////////////////////////////////////////////////////////////////////
public admin_sound_add(HLCommand, HLData, HLUserName, UserIndex)
{
	new Data[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new Word[TOK_LENGTH];
	new Wav[TOK_LENGTH];
	new Text[MAX_TEXT_LENGTH];
	new bGotOne = 0;
	new i;
	new j;
 
	convert_string(HLData, Data, MAX_DATA_LENGTH);
	convert_string(HLUserName, User, MAX_NAME_LENGTH);
 
	// Remove outside quotes
	strstripquotes(Data);
	// Parse command line
	strsplit(Data, ";", Word, TOK_LENGTH, Wav, TOK_LENGTH);
	// Remove whitespace
	strtrim(Word, " ^t");
	strtrim(Wav, " ^t");
 
	if (strlen(Word) == 0 || strlen(Wav) == 0)
	{
		selfmessage("Invalid format.");
		selfmessage("USAGE: admin_sound_add ^"<keyword>;<dir\wav>^"");
		selfmessage("USAGE: admin_sound_add ^"<parameter>;<value>^"");
		return PLUGIN_HANDLED;
	}
 
	// First look for special parameters
	if (!strcasecmp(Word, "SND_KICK") || !strcasecmp(Word, "SND_MAX")) // backwards compatibility
	{
		SND_MAX = strtonum(Wav);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "SND_WARN"))
	{
		SND_WARN = strtonum(Wav);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "SND_JOIN"))
	{
		strcpy(SND_JOIN, Wav, TOK_LENGTH);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "SND_EXIT"))
	{
		strcpy(SND_EXIT, Wav, TOK_LENGTH);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "SND_PUNISH"))
	{
		strcpy(SND_PUNISH, Wav, TOK_LENGTH);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "SND_DELAY"))
	{
		SND_DELAY = strtonum(Wav);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "SND_SPLIT"))
	{
		SND_SPLIT = strtonum(Wav);
		bGotOne = 1;
	}
	else if (!strcasecmp(Word, "EXACT_MATCH"))
	{
		EXACT_MATCH = strtonum(Wav);
		bGotOne = 1;
	}
 
	if (bGotOne)
	{
		// Do some error checking on the user-input numbers
		ErrorCheck();
		return PLUGIN_HANDLED;
	}
 
	// Loop once for each keyword
	for (i=0; i<MAX_KEYWORDS; i++)
	{
		// If an empty string, then break this loop
		if (strlen(WordWavCombo[i]) == 0)
			break;
 
		// If we find a match, then add on the new wav data
		if (strncmp(Word, WordWavCombo[i], TOK_LENGTH) == 0)
		{
			// See if the wav already exists
			for (j = 1; j <= MAX_RANDOM; j++)
			{
				// If an empty string, then break this loop
				if (strlen(WordWavCombo[i][TOK_LENGTH*j]) == 0)
					break;
 
				// See if this is the same as the new wav
				if (strncmp(Wav, WordWavCombo[i][TOK_LENGTH*j], TOK_LENGTH) == 0)
				{
					snprintf(Text, MAX_TEXT_LENGTH, "%s; %s already exists.", Word, Wav);
					selfmessage(Text);
					return PLUGIN_HANDLED;
				}
			}
 
			// If we reached the end, then there is no room
			if (j >= MAX_RANDOM)
				selfmessage("No room for new wav. Increase MAX_RANDOM and recompile.");
			else
			{
				// Word exists, but Wav is new to the list, so add entry
				strcpy(WordWavCombo[i][TOK_LENGTH*j], Wav, TOK_LENGTH);
				snprintf(Text, MAX_TEXT_LENGTH, "%s successfully added to %s", Wav, Word);
				selfmessage(Text);
			}
			return PLUGIN_HANDLED;
		}
	}
 
	// If we reached the end, then there is no room
	if (i == MAX_KEYWORDS)
		selfmessage("No room for new Word/Wav combo. Increase MAX_KEYWORDS and recompile.");
	else
	{
		// Word/Wav combo is new to the list, so make a new entry
		strcpy(WordWavCombo[i][TOK_LENGTH*0], Word, TOK_LENGTH);
		strcpy(WordWavCombo[i][TOK_LENGTH*1], Wav, TOK_LENGTH);
		snprintf(Text, MAX_TEXT_LENGTH, "%s; %s successfully added.", Word, Wav);
		selfmessage(Text);
	}
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Instead of using admin_help, which uses static data, admin_sound_help
// always lists the most up-to-date information because the Word/Wav list can
// change  the middle of gameplay. admin_sound_help lists all admin_sound
// commands and keywords to the user.
//
// Usage: admin_sound_help
//////////////////////////////////////////////////////////////////////////////
public admin_sound_help(HLCommand,HLData,HLUserName,UserIndex)
{
	new User[MAX_NAME_LENGTH];
	new Text[MAX_TEXT_LENGTH] = "";
	new i = 0;
	new j = 0;
 
	convert_string(HLUserName, User, MAX_NAME_LENGTH);
 
/*	selfmessage("admin_sound_add ^"<keyword>;<dir\wav>^" : Adds a Word/Wav combo to the sound list. Must use quotes.");
	selfmessage("admin_sound_help: Shows this text.");
	selfmessage("admin_sound_off: Turns off sounds.");
	selfmessage("admin_sound_on: Turns on sounds.");
	selfmessage("admin_sound_play <dir\wav> : Plays sound to all users.");
	selfmessage("admin_sound_reload [filename] : Reloads config file. Filename is optional.");
	selfmessage("admin_sound_remove ^"<keyword>;[dir\wav]^" : Removes a Word/Wav combo from the sound list. Must use quotes.");
	selfmessage("admin_sound_write <filename> : Writes the current sound configuration to filename.");
*/	selfmessage("say <keyword>: Plays a sound. Keywords are listed below:");
 
	// Loop once for each keyword
	for (i = 0; i < MAX_KEYWORDS; i++)
	{
		// If an invalid string, then break this loop
		if ((strlen(WordWavCombo[i]) == 0) || (strlen(WordWavCombo[i]) > TOK_LENGTH))
			break;
 
		if ((WordWavCombo[i][0] != '@') || (access(ACCESS_ADMINSOUNDS, User)))
		{
			strcat(Text, WordWavCombo[i], MAX_TEXT_LENGTH);
			strcat(Text, " ^t ^t", MAX_TEXT_LENGTH);
			j++;
		}
 
		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
			selfmessage(Text);
			Text[0] = 0;
		}
	}
 
	if (strlen(Text) != 0)
		selfmessage(Text);	
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Turns off the playing of the wav files for this plugin only.
//
// Usage: admin_sound_off
//////////////////////////////////////////////////////////////////////////////
public admin_sound_off(HLCommand, HLData, HLUserName, UserIndex)
{
	bSoundsEnabled = 0;
	say("Sank Sounds Plugin has been deactivated!");
//	playsoundall("fvox/six(e50) safe_day(s90) ten(s65) (t30) communications_on(e1)");
	playsoundall("^"fvox/south(e70) seconds(s60) (p96) deactivated^"");
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Turns on the playing of the wav files for this plugin only.
//
// Usage: admin_sound_on
//////////////////////////////////////////////////////////////////////////////
public admin_sound_on(HLCommand, HLData, HLUserName, UserIndex)
{
	bSoundsEnabled = 1;
	say("Sank Sounds Plugin has been activated!");
//	playsoundall("fvox/six(e50) safe_day(s90) ten(s65) (t30) communications_on(e1) (t1) south(e70) seconds(s60) (p96) activated");
	playsoundall("^"fvox/south(e70) seconds(s60) (p96) activated^"");
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Plays a sound to all players
//
// Usage: admin_sound_play <dir\wav>
//////////////////////////////////////////////////////////////////////////////
public admin_sound_play(HLCommand, HLData, HLUserName, UserIndex)
{
	new Data[MAX_DATA_LENGTH];
	convert_string(HLData, Data, MAX_DATA_LENGTH);
 
	playsoundall(Data);
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Reloads the Word/Wav combos from filename.
//
// Usage: admin_sound_reload [filename]
//////////////////////////////////////////////////////////////////////////////
public admin_sound_reload(HLCommand, HLData, HLUserName, UserIndex)
{
	new parsefile[MAX_DATA_LENGTH];
 
	convert_string(HLData, parsefile, MAX_DATA_LENGTH);
 
	// Initialize WordWavCombo[][][] array
	new i;
	for (i = 0; i < MAX_KEYWORDS; i++)
		WordWavCombo[i][0] = 0;
 
	parse_sound_file(parsefile);
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Removes a Word/Wav combo from the list. You must specify a keyword, but it
// is not necessary to specify a wav if you want to remove all wavs associated
// with that keyword. Surrounding quotes are required.
//
// Usage: admin_sound_remove "<keyword>;[wav]"
//////////////////////////////////////////////////////////////////////////////
public admin_sound_remove(HLCommand, HLData, HLUserName, UserIndex)
{
	new Data[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new Word[TOK_LENGTH];
	new Wav[TOK_LENGTH];
	new Text[MAX_TEXT_LENGTH];
	new iCurWord;
	new jCurWav;
 
	convert_string(HLData, Data, MAX_DATA_LENGTH);
	convert_string(HLUserName, User, MAX_NAME_LENGTH);
 
	// Parse command line
	strsplit(Data, ";", Word, TOK_LENGTH, Wav, TOK_LENGTH);
	// Remove whitespace
	strtrim(Word, " ^t");
	strtrim(Wav, " ^t");
 
	if (strlen(Data) == 0)
	{
		selfmessage("Invalid format.");
		selfmessage("USAGE: admin_sound_remove ^"<keyword>;[dir\wav]^"");
		return PLUGIN_HANDLED;
	}
 
	// Loop once for each keyword
	for (iCurWord = 0; iCurWord < MAX_KEYWORDS; iCurWord++)
	{
		// If an empty string, then break this loop, we're at the end
		if (strlen(WordWavCombo[iCurWord]) == 0)
			break;
 
		// Look for a Word match
		if (strncmp(Word, WordWavCombo[iCurWord], TOK_LENGTH) == 0)
		{
			// If no wav was specified, then remove the whole word's entry
			if (strlen(Wav) == 0)
			{
				// Keep looping i, copying the next into the current
				for (; iCurWord < MAX_KEYWORDS; iCurWord++)
				{
					// If we're about to copy a string that doesn't exist,
					//  then just erase the last string instead of copying.
					if (iCurWord >= MAX_KEYWORDS - 1)
					{
						// Delete the last word string 
						WordWavCombo[iCurWord][0] = 0;
 
						// We reached the end
						snprintf(Text, MAX_TEXT_LENGTH, "%s successfully removed.", Word);
						selfmessage(Text);
						return PLUGIN_HANDLED;
					}
					else
					{
						// Copy the next string over the current string
						for (jCurWav = 0; jCurWav < TOK_LENGTH * (MAX_RANDOM+1); jCurWav++)
							WordWavCombo[iCurWord][jCurWav] = WordWavCombo[iCurWord+1][jCurWav];
					}
				}
			}
			else
			{
				// Just remove the one wav, if it exists
				for (jCurWav = 1; jCurWav <= MAX_RANDOM; jCurWav++)
				{
					// If an empty string, then break this loop, we're at the end
					if (strlen(WordWavCombo[iCurWord][TOK_LENGTH*jCurWav]) == 0)
						break;
 
					// Look for a Wav match
					if (strncmp(Wav, WordWavCombo[iCurWord][TOK_LENGTH*jCurWav], TOK_LENGTH) == 0)
					{
						for (; jCurWav <= MAX_RANDOM; jCurWav++)
						{
							// If this is the only wav entry, then remove the entry altogether
							if ((jCurWav == 1) && (strlen(WordWavCombo[iCurWord][TOK_LENGTH*(jCurWav+1)]) == 0))
							{
								// Keep looping i, copying the next into the current
								for (; iCurWord < MAX_KEYWORDS; iCurWord++)
								{
									// If we're about to copy a string that doesn't exist,
									//  then just erase the last string instead of copying.
									if (iCurWord >= MAX_KEYWORDS-1)
									{
										// Delete the last word string 
										WordWavCombo[iCurWord][0] = 0;
 
										// We reached the end
										snprintf(Text, MAX_TEXT_LENGTH, "%s successfully removed.", Word);
										selfmessage(Text);
										return PLUGIN_HANDLED;
									}
									else
									{
										// Copy the next string over the current string
										for (jCurWav = 0; jCurWav < TOK_LENGTH * (MAX_RANDOM+1); jCurWav++)
											WordWavCombo[iCurWord][jCurWav] = WordWavCombo[iCurWord+1][jCurWav];
									}
								}
							}
 
							// If we're about to copy a string that doesn't exist,
							//  then just erase the last string instead of copying.
							if (jCurWav >= MAX_RANDOM)
							{
								// Delete the last wav string
								WordWavCombo[iCurWord][TOK_LENGTH*jCurWav] = 0;
 
								// We reached the end
								snprintf(Text, MAX_TEXT_LENGTH, "%s successfully removed from %s.", Wav, Word);
								selfmessage(Text);
								return PLUGIN_HANDLED;
							}
							else
							{
								// Copy the next string over the current string
								strcpy(WordWavCombo[iCurWord][TOK_LENGTH*jCurWav], WordWavCombo[iCurWord][TOK_LENGTH*(jCurWav+1)], TOK_LENGTH);
							}
						}
					}
				}
 
				// We reached the end for this word, and the wav didn't exist
				snprintf(Text, MAX_TEXT_LENGTH, "%s not found.",  Wav);
				selfmessage(Text);
				return PLUGIN_HANDLED;
			}
		}
	}
 
	// We reached the end, and the word didn't exist
	snprintf(Text, MAX_TEXT_LENGTH, "%s not found.", Word);
	selfmessage(Text);
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Saves the current configuration of Word/Wav combos to filename for possible
// reloading at a later time. You cannot overwrite the default file.
//
// Usage: admin_sound_write <filename>
//////////////////////////////////////////////////////////////////////////////
public admin_sound_write(HLCommand, HLData, HLUserName, UserIndex)
{
	new savefile[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new Text[MAX_TEXT_LENGTH];
	new TimeStamp[MAX_TEXT_LENGTH];
	new bSuccess = 1;
	new i;
	new j;
 
	convert_string(HLData, savefile, MAX_DATA_LENGTH);
	convert_string(HLUserName, User, MAX_NAME_LENGTH);
	servertime(TimeStamp, MAX_TEXT_LENGTH, "%H:%M %B %d, %Y");
 
	// If the filename is NULL, then that's bad.
	if (strlen(savefile) == 0)
	{
		selfmessage("[plugin_sank_sounds] You must specify a filename.");
 
		return PLUGIN_HANDLED;
	}
	// If the filename is the same as the default FILENAME, then that's bad.
	if (strcasecmp(savefile, FILENAME) == 0)
	{
		selfmessage("[plugin_sank_sounds] Illegal write to default sound config file.");
		selfmessage("[plugin_sank_sounds] Specify a different filename.");
 
		return PLUGIN_HANDLED;
	}
 
 
	/************ File should have the following format: **************
	# TimeStamp:		07:15 January 15, 2001
	# File created by:	[SPU]Crazy_Chevy
 
	# Important parameters:
	SND_MAX;	20
	SND_WARN;	17
	SND_JOIN;	
	SND_EXIT;	misc\comeagain.wav
 
	# Word/Wav 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
 
	******************************************************************/
 
	// See if we have file_access_write
	if (getvar("file_access_write") == 0)
	{
		snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] CVAR file_access_write is set incorrectly for admin_sound_write.");
		selfmessage(Text);
 
		return PLUGIN_HANDLED;
	}
 
	// check for other file errors
	if (!resetfile(savefile))
	{
		snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] unknown file error with %s", savefile);
		selfmessage(Text);
 
		return PLUGIN_HANDLED;
	}
 
	// We now assume that we can write to the file, so here we go!
	snprintf(Text, MAX_TEXT_LENGTH, "# TimeStamp:^t^t%s", TimeStamp);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "# File created by:^t%s", User);
	writefile(savefile, Text);
	writefile(savefile, "");	// blank line
	writefile(savefile, "# Important parameters:");
	snprintf(Text, MAX_TEXT_LENGTH, "SND_MAX;^t%d", SND_MAX);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "SND_WARN;^t%d", SND_WARN);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "SND_JOIN;^t%s", SND_JOIN);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "SND_EXIT;^t%s", SND_EXIT);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "SND_PUNISH;^t%s", SND_PUNISH);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "SND_DELAY;^t%d", SND_DELAY);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "SND_SPLIT;^t%d", SND_SPLIT);
	writefile(savefile, Text);
	snprintf(Text, MAX_TEXT_LENGTH, "EXACT_MATCH;^t%d", EXACT_MATCH);
	writefile(savefile, Text);
	writefile(savefile, "");	// blank line
	writefile(savefile, "# Word/Wav combinations:");
 
	for (i = 0; i < MAX_KEYWORDS && bSuccess; i++)
	{
		// See if we reached the end
		if (strlen(WordWavCombo[i]) == 0)
			break;
		// I guess not, so format up a string to write
 
		// First, add the keyword
		snprintf(Text, MAX_TEXT_LENGTH, "%s;^t", WordWavCombo[i]);
 
		// Then add all the wavs
		for (j = 1; j < MAX_RANDOM && strlen(WordWavCombo[i][TOK_LENGTH*j]); j++)
			snprintf(Text, MAX_TEXT_LENGTH, "%s%s;", Text, WordWavCombo[i][TOK_LENGTH*j]);
 
		// Now write the formatted string to the file
		bSuccess = writefile(savefile, Text);
		// And loop for the next wav
	}
 
	snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] Configuration successfully written to %s.", savefile);
	selfmessage(Text);
 
	return PLUGIN_HANDLED;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Checks the input variables for invalid values
//////////////////////////////////////////////////////////////////////////////
ErrorCheck()
{
	// Can't have negative delay between sounds
	if (SND_DELAY < 0)
	{
		log("[plugin_sank_sounds] SND_DELAY cannot be negative.");
		log("[plugin_sank_sounds] SND_DELAY set to default value.");
		SND_DELAY = 0;
	}
 
	// If SND_PUNISH is blank or zero, set to default command: admin_kick
	if (strlen(SND_PUNISH) == 0 || SND_PUNISH[0] == '0')
		log("[plugin_sank_sounds] SND_PUNISH set to default: admin_kick");
 
	// If SND_MAX is zero, then sounds quota is disabled.
	if (SND_MAX <= 0)
	{
		SND_MAX = 0;	// in case it was negative
		log("Sound quota disabled.");
	}
 
	// 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)
	{
		if (SND_MAX > 3)
			SND_WARN = SND_MAX - 3;
		else
			SND_WARN = SND_MAX - 1;
 
		log("[plugin_sank_sounds] SND_WARN cannot be set to zero.");
		log("[plugin_sank_sounds] SND_WARN set to default value.");
	}
 
	// And finally, if they want to warn after a person has been
	//  kicked, that's silly, so we'll fix it.
	else if (SND_MAX < SND_WARN)
	{
		if (SND_MAX > 3)
			SND_WARN = SND_MAX - 3;
		else
			SND_WARN = SND_MAX - 1;
 
		log("[plugin_sank_sounds] SND_WARN cannot be higher than SND_MAX.");
		log("[plugin_sank_sounds] SND_WARN set to default value.");
	}
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Everything a person says goes through here, and we determine if we want to
// play a sound or not.
//
// Usage: say <anything>
//////////////////////////////////////////////////////////////////////////////
public HandleSay(HLCommand, HLData, HLUserName, UserIndex)
{
	// If sounds are not enabled, then skip this whole thing
	if (!bSoundsEnabled)
		return PLUGIN_CONTINUE;
 
	new i;
	new Speech[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new Text[MAX_TEXT_LENGTH];
	new ListIndex = -1;
 
	convert_string(HLData, Speech, MAX_DATA_LENGTH);
	convert_string(HLUserName, User, MAX_NAME_LENGTH);
	strstripquotes(Speech);
 
 
	if (strlen(Speech) == 0)
		return PLUGIN_CONTINUE;
 
	if (systemtime() - LastSoundTime < SND_DELAY)
	{
		messageex(User, "Minimum sound delay time not yet reached.", print_console);
		return PLUGIN_CONTINUE;
	}
 
	// Remove @ from user's speech, incase non-admin is trying to impersonate real admin
	strtrim(Speech, "@", 0);
 
	// Check to see if what the player said is a trigger for a sound
	for (i = 0; i < MAX_KEYWORDS; i++)
	{
		strcpy(Text, WordWavCombo[i], TOK_LENGTH);
 
		// Remove the possible @ sign from beginning (for admins only).
		if (access(ACCESS_ADMINSOUNDS, User))
			strtrim(Text, "@", 0);
 
		if ((!strcasecmp(Speech, Text)) ||								// exact string match
			((EXACT_MATCH == 0) && (strcasestrx(Speech, Text) != -1)))	// string within string match
		{
			ListIndex = i;
			break;
		}
	}
 
	// If what the player said is a sound trigger, then handle it.
	if (ListIndex != -1)
	{
		#if DEBUG
		printf("Checking Quota for %s:  %s in %s^n", User, Text, Speech);
		#endif
 
		// If the user has not exceeded their quota, then play a wav
		if (!QuotaExceeded(User, UserIndex))
		{
			new rand = random(MAX_RANDOM);
			new timeout;
			new playWav[TOK_LENGTH];
			new iDead;
 
			// This for loop runs around until it finds a real file to play
			// Defaults to the first wav file, if no file is found at random.
			for (timeout = MAX_RANDOM;					// Initial condition
				 timeout >= 0 && strlen(playWav) == 0;	// While these are true
				 timeout--, rand = random(MAX_RANDOM))	// Update each iteration
			{
				// If for some reason we never find a file
				//  then default to the first wav entry
				if (!timeout)
					rand = 0;
 
				strcpy(playWav, WordWavCombo[ListIndex][(rand+1)*TOK_LENGTH], TOK_LENGTH);
 
				// If this wav was an admin-only wav, but User is not an admin, then skip this one
				if ((strtrim(playWav, "@", 0) == 1) && !access(ACCESS_ADMINSOUNDS, User))
					playWav[0] = 0;
			}
 
			LastSoundTime = systemtime();
			if (SND_SPLIT)
			{
				if (playerinfo(UserIndex, Text, MAX_TEXT_LENGTH, _, _, _, iDead, _) == 1)
					playsoundall(playWav, iDead);
				return PLUGIN_CONTINUE;
			}
			else
			{
				snprintf(Text, MAX_TEXT_LENGTH, "%s :    %s", User, Speech);
				say(Text);
				playsoundall(playWav);
				return PLUGIN_HANDLED;
			}
		}
	}
	else if ((!strcasecmp(Speech, "aybabtu")) ||							// exact string match
			((EXACT_MATCH == 0) && (strcasestrx(Speech, "aybabtu") != -1)))	// string within string match
	{
		playsoundall("^"(p96) all(t20) your(t20) base(t10) are(t20) be(t40) lock(e50) handling(s70) to us us(s75) us(s75)^"");
		return PLUGIN_HANDLED;
	}
 
	return PLUGIN_CONTINUE;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// This is called when the player leaves the console after connection is
// complete.  This allows the connecting player to hear the sound as well as
// the players already in the game.
//////////////////////////////////////////////////////////////////////////////
public HandleSpecMode(HLCommand, HLData, HLUserName, UserIndex)
{
	playsoundall(SND_JOIN);
}
 
 
//////////////////////////////////////////////////////////////////////////////
// Parses the sound file specified by loadfile. If loadfile is empty, then
// it parses the default FILENAME.
//
// Returns 0 if parsing was successful
// Returns 1 if parsing failed
// Returns -1 otherwise
//
// Usage:
// admin_sound_reload <filename>
//////////////////////////////////////////////////////////////////////////////
parse_sound_file(loadfile[MAX_TEXT_LENGTH] = "")
{
	new GotLine;
	new iLineNum = 0;
	new strLineBuf[MAX_TEXT_LENGTH];
	new WadOstrings[(MAX_RANDOM+1)*TOK_LENGTH]; // same as [MAX_RANDOM][TOK_LENGTH]
	new ListIndex = 0;
	new Text[MAX_TEXT_LENGTH];
 
	/************ File should have the following format: **************
 
	# Set the necessary variables
	SND_MAX;	20
	SND_WARN;	17
	SND_EXIT;	misc\comeagain.wav
 
	# Now give the sound list
	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
 
	******************************************************************/
 
	if (strlen(loadfile) == 0)
	{
		// No file specified, use default file
		strcpy(loadfile, FILENAME, MAX_TEXT_LENGTH);
		if (!fileexists(loadfile))
			strcpy(loadfile, "SND-LIST.CFG", MAX_TEXT_LENGTH); // backwards compatibility
	}
 
	if (fileexists(loadfile))
	{
		new i;
 
		GotLine = readfile(loadfile, strLineBuf, iLineNum, MAX_TEXT_LENGTH);
 
		if (GotLine <= 0)
		{
			// If file access is already set correctly...
			if (getvar("file_access_read") == 1)
				snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] Unable to read from %s file.", loadfile);
			else
				snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] CVAR file_access_read is set incorrectly.");
 
			log(Text);
			return -1;
		}
 
		// Initialize WordWavCombo[][][] array before using it
		for(i = 0; i < MAX_KEYWORDS; i++)
			WordWavCombo[i][0] = 0;
 
		while (GotLine > 0)
		{
 
			if (ListIndex >= MAX_KEYWORDS)
			{
				log("[plugin_sank_sounds] Sound list truncated. Increase MAX_KEYWORDS.");
				printf("[plugin_sank_sounds] Stopped parsing %s file.^n", loadfile);
				break;
			}
 
			// As long as the line isn't commented out, and isn't blank, then process it.
			if ((strncmp(strLineBuf, "#", 1) != 0) && (strncmp(strLineBuf, "//", 2) != 0) && (strlen(strLineBuf) != 0))
			{
				// Take up to MAX_RANDOM wav files for each keyword, each separated by a ';' 
 
				// Right now we fill the big WadOstrings[] with the information from the file.
				for(i = 0; i <= MAX_RANDOM; i++)
					strsep(strLineBuf, ";", WadOstrings[TOK_LENGTH*i], TOK_LENGTH, strLineBuf, MAX_TEXT_LENGTH);
 
				// If we finished MAX_RANDOM times, and strRest still has contents
				//  then we should have a bigger MAX_RANDOM
				if(strlen(strLineBuf) != 0)
				{
					log("[plugin_sank_sounds] Sound list partially truncated. Increase MAX_RANDOM.");
					printf("[plugin_sank_sounds] Continuing to parse %s file.^n", loadfile);
				}
 
				// Now remove any spaces or tabs from around the strings -- clean them up
				for (i = 0; i < MAX_RANDOM; i++)
					strtrim(WadOstrings[TOK_LENGTH*i], " ^t");
 
 
				// First look for special parameters
				if (!strcasecmp(WadOstrings, "SND_MAX") || !strcasecmp(WadOstrings, "SND_KICK")) // backwards compatibility
					SND_MAX = strtonum(WadOstrings[TOK_LENGTH*1]);
				else if (!strcasecmp(WadOstrings, "SND_WARN"))
					SND_WARN = strtonum(WadOstrings[TOK_LENGTH*1]);
				else if (!strcasecmp(WadOstrings, "SND_JOIN"))
					strcpy(SND_JOIN, WadOstrings[TOK_LENGTH*1], TOK_LENGTH);
				else if (!strcasecmp(WadOstrings, "SND_EXIT"))
					strcpy(SND_EXIT, WadOstrings[TOK_LENGTH*1], TOK_LENGTH);
				else if (!strcasecmp(WadOstrings, "SND_PUNISH"))
					strcpy(SND_PUNISH, WadOstrings[TOK_LENGTH*1], TOK_LENGTH);
				else if (!strcasecmp(WadOstrings, "SND_DELAY"))
					SND_DELAY = strtonum(WadOstrings[TOK_LENGTH*1]);
				else if (!strcasecmp(WadOstrings, "SND_SPLIT"))
					SND_SPLIT = strtonum(WadOstrings[TOK_LENGTH*1]);
				else if (!strcasecmp(WadOstrings, "EXACT_MATCH"))
					EXACT_MATCH = strtonum(WadOstrings[TOK_LENGTH*1]);
 
				// If it wasn't one of those essential parameters, then it should be
				//  a Keyword/Wav combo, so we'll treat it as such by copying it from our
				//  temporary structure into our global structure, WordWavCombo[][][]
				else
				{
					// Now we must transfer the contents of WadOstrings[] to
					//  our global data structure, WordWavCombo[Index][]
					//  with a really tricky "string copy"
					for (i = 0; i < MAX_RANDOM*TOK_LENGTH; i++)
						WordWavCombo[ListIndex][i] = WadOstrings[i];
 
					ListIndex++;
				}
			}
 
			// Initialize variables for next time  by clearing all the
			//  strings in the WadOstrings[]
			for (i = 0; i < MAX_RANDOM; i++)
				WadOstrings[i*TOK_LENGTH] = 0;
 
			// Read in the next line from the file
			GotLine = readfile(loadfile, strLineBuf, ++iLineNum, MAX_TEXT_LENGTH);
		}
 
		// 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();
 
		// Log some info for the nosey admin
		snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] Sound quota set to %i", SND_MAX);
		selfmessage(Text);
 
		#if DEBUG
		print_matrix();
		printf("[plugin_sank_sounds] Done parsing %s file.^n", loadfile);
		#endif
	}
	else // file exists returned false, meaning the file didn't exist
	{
		snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] Cannot find %s file.", loadfile);
		log(Text);
		selfmessage(Text);
		return 1;
	}
 
	snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] %s successfully loaded.", loadfile);
	selfmessage(Text);
	return 0;
}
 
 
//////////////////////////////////////////////////////////////////////////////
// This function used to be in the SOUND.INC file, but too many people emailed
// me asking where to put the sound.inc file, so I stopped using the file, as
// its usefulness was outlived anyway.
//
// Usage:
// playsoundall("misc/doh.wav");			// play
// playsoundall("^"target destroyed^"");	// speak
//////////////////////////////////////////////////////////////////////////////
stock playsoundall(sound[], IfDead = -1)
{
	new maxplayers = maxplayercount();
	new Name[MAX_NAME_LENGTH];
	new i;
 
	if (sound[0] == '^"')
	{
		for (i = 1; i <= maxplayers; i++)
		{
			new iDead;
			if (playerinfo(i, Name, MAX_NAME_LENGTH, _, _, _, iDead, _) == 1)
			{
				// Use the "speak" command
				if (IfDead == -1)
					speakto(Name, sound);
				else if (IfDead == iDead)
					speakto(Name, sound);
			}
		}
	}
	else
	{
		for (i = 1; i <= maxplayers; i++)
		{
			new iDead;
			if (playerinfo(i, Name, MAX_NAME_LENGTH, _, _, _, iDead, _) == 1)
			{
				// Use the "play" command
				if (IfDead == -1)
					playsound(Name, sound);
				else if (IfDead == iDead)
					playsound(Name, sound);
			}
		}
	}
}
 
 
#if DEBUG
//////////////////////////////////////////////////////////////////////////////
// Prints out word wav combo matrix for debugging purposes. Kinda cool, even
// if you're not really debugging.
//
// Usage:
// admin_sound_debug
// admin_sound_reload <filename>
//////////////////////////////////////////////////////////////////////////////
public print_matrix()
{
	printf("SND_MAX: %d^n", SND_MAX);
	printf("SND_WARN: %d^n", SND_WARN);
	printf("SND_JOIN: %s^n", SND_JOIN);
	printf("SND_EXIT: %s^n", SND_EXIT);
	printf("SND_PUNISH: %s^n", SND_PUNISH);
	printf("SND_DELAY: %d^n", SND_DELAY);
	printf("SND_SPLIT: %d^n", SND_SPLIT);
	printf("EXACT_MATCH: %d^n", EXACT_MATCH);
 
	new i;
	new j;
	// Print out the matrix of sound data, so we got what we think we did
	for (i = 0; i < MAX_KEYWORDS; i++)
	{
		if (strlen(WordWavCombo[i]) != 0)
		{
//			printf("^n");
			printf("^n[%d] ^"%s^"", i, WordWavCombo[i][0]);
			for (j = 1; j < MAX_RANDOM+1; j++)
			{
				if (strlen(WordWavCombo[i][j*TOK_LENGTH]) != 0)
//					printf("[%d] [%d] ^"%s^"^n", i, j, WordWavCombo[i][j*TOK_LENGTH]);
					printf(" ^"%s^"", WordWavCombo[i][j*TOK_LENGTH]);
			}
		}
	}
	printf("^n");
 
	return PLUGIN_HANDLED;
}
#endif
 
 
//////////////////////////////////////////////////////////////////////////////
// Returns 0 if the user is allowed to say things
// Returns 1 and kicks the user if the quota has been exceeded.
//////////////////////////////////////////////////////////////////////////////
QuotaExceeded(User[], UserIndex)
{
	// If the sound limitation is disabled, then return happily.
	if (SND_MAX == 0)
		return 0;
 
	// If the user is not really a user, then maybe a bot, maybe a bug...?
	if (UserIndex < 1 || UserIndex > MAX_PLAYERS)
		return 0;
 
	new Text[MAX_TEXT_LENGTH] = "";
	new HowManyLeft = SND_MAX - SndCount[UserIndex];
 
	if (check_immunity(User) == 0)
	{
		// Increment their playsound count
		SndCount[UserIndex] = SndCount[UserIndex] + 1;
 
		if (SndCount[UserIndex] >= SND_MAX)
		{
			message(User, "You were warned, but did not quit playing sounds");
			snprintf(Text, MAX_TEXT_LENGTH, "Punished %s for saying too much", User);
			say(Text);
 
			if (strlen(SND_PUNISH)!=0)
			{
				g_User[UserIndex]=1;
				//If we have custom SND_PUNISH command, then try to execute it
				if (getvar("file_access_write") == 0)
				{
					//If custom punish specified, but file access write is set incorrectly...
					snprintf(Text, MAX_TEXT_LENGTH, "[plugin_sank_sounds] CVAR file_access_write is set incorrectly for SND_PUNISH.");
					log(Text);
					kick(User);
				}
				else
				{
					if(!fileexists(punishfile)){
						//If custom punish specified, then check each file access function
						snprintf(Text, MAX_TEXT_LENGTH, "admin_command snd_punish");
						if (!writefile(punishfile, Text,1))
						{
							log("[plugin_sank_sounds] unknown file error while executing SND_PUNISH command");
							kick(User);
						}else{
							snprintf(Text, MAX_TEXT_LENGTH, "exec %s", punishfile);
							exec(Text);
						}
					}
					else
					{
						snprintf(Text, MAX_TEXT_LENGTH, "exec %s", punishfile);
						exec(Text);
					}
				}
			}
			else
			{
				//If we have no custom SND_PUNISH command, then use the default kick
				kick(User);
			}
 
			return 1;
		}
		else if (SndCount[UserIndex] >= SND_WARN)
		{
			messageex(User, "You have almost used up your sound quota. Stop.", print_center);
			messageex(User, "You have almost used up your sound quota. Stop.", print_console);
			snprintf(Text, MAX_TEXT_LENGTH, "You have %d left before you get punished.", HowManyLeft);
			messageex(User, Text, print_center);
			messageex(User, Text, print_console);
		}
	}
	return 0;
}
 
public snd_punish(HLCommand,HLData,HLUserName,UserIndex) {
	new Player[MAX_NAME_LENGTH];
	new i;
	new maxplayers=maxplayercount();
	for(i=1;i<=maxplayers;i++){
		if(g_User[i]==1){
			if(playerinfo(i,Player,MAX_NAME_LENGTH)){
				plugin_exec(SND_PUNISH,Player);
				g_User[i]=0;
				break;
			}
		}
	}
	return PLUGIN_HANDLED;
}