#include <core>
#include <string>
#include <admin>
#include <adminlib>
#include <console>
 
ReadLine (file[], lineNo, buffer[], maxlen);
CopyToken (source[], start, token[], maxlen);
 
 
#define	DEFAULT_ACCESS			ACCESS_SAY
#define DEFAULT_WRITE_ACCESS	ACCESS_SAY
#define DONE					-1
#define SPACE					32
#define MAX_COLUMN				60
#define MAX_EXEC_LEN			92
#define MAX_UNSAVED_WORDS		32
#define MAX_OUT_LINES			20
#define	MAX_SOUNDS				256
#define MAX_SOUND_LEN			128
#define MAX_TOKEN_LEN			16
#define MAX_WORD_LEN			24
 
new OPTION_ADD_SOUND[]		= "-addsound";
new OPTION_ADD_WORD[]		= "-addword";
new OPTION_FLUSH[]			= "-flush";
new OPTION_HELP[]			= "-help";
new OPTION_MATCH[]			= "-match";
new OPTION_RELOAD[]			= "-reload";
new OPTION_SAVE_SOUNDS[]	= "-savesounds";
new OPTION_SAVE_WORDS[]		= "-savewords";
new OPTION_SOUNDS[]			= "-sounds";
new OPTION_SET_SOUND[]		= "-trysound";
new OPTION_SET_WORD[]		= "-tryword";
new OPTION_UNSAVED[]		= "-unsaved";
new OPTION_WORDS[]			= "-words";
new OPTION_VOX[]			= "-vox";
 
new LINES_FILE[]			= "yak_lines.txt";
new MY_NAME[]				= "admin_yak";
new SOUNDS_FILE[]			= "yak_sounds.txt";
new WORDS_FILE[]			= "yak_words.txt";
new VERSION[]				= "0.2";
new VOX_WORDS_FILE[]		= "vox_words.txt";
new YAK_ACCESS_VAR[]		= "yak_access_level";
new YAK_WRITE_ACCESS_VAR[]	= "yak_write_access_level";
 
new NumSounds;
new SavedSounds[MAX_SOUNDS];							// 1=sound is in file, 0=not
new Sounds[MAX_SOUNDS][MAX_SOUND_LEN];					// vox sounds
new Tokens[MAX_SOUNDS][MAX_TOKEN_LEN];					// sound keys
new UnsavedWords[MAX_UNSAVED_WORDS][MAX_WORD_LEN];		// word keys
new UnsavedSounds[MAX_UNSAVED_WORDS][MAX_SOUND_LEN];	// sounds for words
 
 
 
 
public plugin_init() 
{
	new i;
 
	i = getvar (YAK_ACCESS_VAR);
	if (i == 0)
		i = DEFAULT_ACCESS;
 
	plugin_registerinfo("Yak tester", "Speak & Spell for HL!.", VERSION);
	plugin_registercmd (MY_NAME, "HandleYak", i);
	plugin_registerhelp(MY_NAME, i, "admin_yak <text>: Speak & Spell for HL! (admin_yak -help for details.");
 
	ReadSounds();
 
	for (i = 0; i < MAX_UNSAVED_WORDS; i++)
	{
		strinit (UnsavedWords[i]);
		strinit (UnsavedSounds[i]);
	}
 
	return PLUGIN_CONTINUE;
}
 
public HandleYak (HLCommand, HLData, HLUser,UserIndex)
{
	new canWrite;
	new Data[MAX_DATA_LENGTH];
	new command[MAX_DATA_LENGTH];
	new i;
 
	convert_string(HLData, Data, MAX_DATA_LENGTH);
 
	i = SkipWS(Data);
	if (i == DONE)
		strcpy (command, OPTION_HELP, MAX_TEXT_LENGTH);
	else
		strcpy (command, Data[i], MAX_DATA_LENGTH);
 
	if (strncmp (command, "-", 1) == 0)
	{
		canWrite = 0;
 
		i = getvar(YAK_WRITE_ACCESS_VAR);
		if (i == 0)
			i = DEFAULT_WRITE_ACCESS;
 
		if (check_auth(i) != 0)
		{
			canWrite = 1;
		}
 
 
		if (canWrite && strcmp (command, OPTION_RELOAD) == 0)
		{
			selfmessage ("Reloading sounds file...");
			NumSounds = 0;
			ReadSounds();
 
			selfprintf ("Read %i sounds from %s", NumSounds, SOUNDS_FILE);
 
			return PLUGIN_HANDLED;
		}
 
		if (strcmp (command, OPTION_HELP) == 0)
		{
			selfprintf ("admin_yak must-know commands:");
			selfprintf ("admin_yak <words> : speaks <words>");
			selfprintf ("admin_yak %s : display this message", OPTION_HELP);
			selfprintf ("admin_yak %s <str> : displays any words matching <str>", OPTION_MATCH);
 
			if (canWrite)
			{
				selfmessage ("");
				selfprintf ("admin_yak commands for trying and adding new sounds & words:");
				selfprintf ("admin_yak %s <sound> [<vox>] : tests a key/vox combination", OPTION_SET_SOUND);
				selfprintf ("admin_yak %s <sound> <vox> : adds a new sound to the sounds file", OPTION_ADD_SOUND);
				selfprintf ("admin_yak %s <word> [<sounds>] : tests a key/sound combination", OPTION_SET_WORD);
				selfprintf ("admin_yak %s <word> <sounds> : adds a new sound to the sounds file", OPTION_ADD_WORD);
				selfprintf ("admin_yak %s : saves all sounds that have been tried but not added", OPTION_SAVE_SOUNDS);
				selfprintf ("admin_yak %s : saves all words that have been tried but not added", OPTION_SAVE_WORDS);
				selfprintf ("admin_yak %s : deletes all unsaved sounds and unsaved words", OPTION_FLUSH);
				selfprintf ("admin_yak %s : reloads sounds file", OPTION_RELOAD);
				selfprintf ("admin_yak %s : dumps unsaved sound and words", OPTION_UNSAVED);
 
				if (getvar("file_access_write") == 0)
				{
					selfprintf ("Write commands are disabled! Use %s and 'condump' instead", OPTION_UNSAVED);
				}
			}
 
			selfmessage ("");
			selfprintf ("admin_yak commands that aren't very interesting:");
			if (canWrite == 0)
			{
				selfprintf ("admin_yak %s : dumps unsaved sound and words", OPTION_UNSAVED);
			}
			selfprintf ("admin_yak %s <str> : displays all sound keys that contain <str>", OPTION_SOUNDS);
			selfprintf ("admin_yak %s <line> : displays known words starting at line <line>", OPTION_WORDS);
			selfprintf ("admin_yak %s <line> : displays built-in VOX words starting at line <line>", OPTION_VOX);
 
			return PLUGIN_HANDLED;
		}
 
		if (strncmp (command, OPTION_WORDS, strlen(OPTION_WORDS)) == 0)
		{
			ListWords (command[strlen(OPTION_WORDS) + 1]);
 
			return PLUGIN_HANDLED;
		}
 
		if (strncmp (command, OPTION_MATCH, strlen(OPTION_MATCH)) == 0)
		{
			MatchWords (command[strlen(OPTION_MATCH) + 1]);
 
			return PLUGIN_HANDLED;
		}
 
		if (strncmp (command, OPTION_SOUNDS, strlen (OPTION_SOUNDS)) == 0)
		{
			ListSounds (command[strlen(OPTION_SOUNDS) + 1]);
 
			return PLUGIN_HANDLED;
		}
 
		if (strncmp (command, OPTION_UNSAVED, strlen (OPTION_UNSAVED)) == 0)
		{
			DumpUnsaved();
 
			return PLUGIN_HANDLED;
		}
 
		if (strncmp (command, OPTION_VOX, strlen (OPTION_VOX)) == 0)
		{
			ListVox (command[strlen(OPTION_VOX) + 1]);
 
			return PLUGIN_HANDLED;
		}
 
		if (canWrite)
		{
			if (strncmp (command, OPTION_SET_SOUND, strlen (OPTION_SET_SOUND)) == 0)
			{
				SetSound (command[strlen(OPTION_SET_SOUND) + 1], OPTION_SET_SOUND, 0, HLUser);
 
				return PLUGIN_HANDLED;
			}
 
			if (strncmp (command, OPTION_ADD_SOUND, strlen (OPTION_ADD_SOUND)) == 0)
			{
				WriteSound (command[strlen(OPTION_ADD_SOUND) + 1], HLUser);
 
				return PLUGIN_HANDLED;
			}
 
			if (strncmp (command, OPTION_SET_WORD, strlen (OPTION_SET_WORD)) == 0)
			{
				SetWord (command[strlen(OPTION_SET_WORD) + 1],  HLUser);
 
				return PLUGIN_HANDLED;
			}
 
			if (strncmp (command, OPTION_ADD_WORD, strlen (OPTION_ADD_WORD)) == 0)
			{
				WriteWord (command[strlen(OPTION_ADD_WORD) + 1], HLUser);
 
				return PLUGIN_HANDLED;
			}
 
			if (strncmp (command, OPTION_SAVE_WORDS, strlen (OPTION_SAVE_WORDS)) == 0)
			{
				SaveWords();
 
				return PLUGIN_HANDLED;
			}
 
			if (strncmp (command, OPTION_SAVE_SOUNDS, strlen (OPTION_SAVE_SOUNDS)) == 0)
			{
				SaveSounds();
 
				return PLUGIN_HANDLED;
			}
 
			if (strncmp (command, OPTION_FLUSH, strlen (OPTION_FLUSH)) == 0)
			{
				FlushUnsaved();
 
				return PLUGIN_HANDLED;
			}
		}
	}
 
	StartYakking (command);
 
	return PLUGIN_HANDLED;
}
 
public YakTimer (Timer, Repeat, HLName, HLParam) 
{
	new Data[MAX_DATA_LENGTH];
 
	convert_string(HLParam, Data, MAX_DATA_LENGTH);
 
	StartYakking (Data);
 
	return PLUGIN_HANDLED;
}
 
StartYakking (command[])
{
	new i;
	new len;
	new previ;
 
	new final[MAX_TEXT_LENGTH];
	new word[MAX_WORD_LEN];
	new words[MAX_TEXT_LENGTH];
 
	strinit (words);
	strinit (final);
 
	if (strncmp (command, "!", 1) == 0)
	{
		// asked to say a sentance..
 
		new count;
		new tmp[MAX_TEXT_LENGTH];
 
		len = strlen(command);
 
		for (count  = ReadLine (LINES_FILE, 0, tmp, MAX_TEXT_LENGTH);
			 count != DONE;
			 count  = ReadLine (LINES_FILE, count, tmp, MAX_TEXT_LENGTH))
		{
			if (strncmp (tmp, command, len) != 0)
				continue;
 
			i = SkipWS (tmp, len);
			if (i == DONE)
			{
				selfprintf ("Bogus format on line %i of %s", count, LINES_FILE);
				return;
			}
 
			strcpy (words, tmp[len], MAX_TEXT_LENGTH);
 
			break;
		}
 
		if (strlen(words) == 0)
			strcpy (words, command, MAX_TEXT_LENGTH);
	}
	else
	{
		strcpy (words, command, MAX_TEXT_LENGTH);
	}
 
	previ = 0;
	len = 0;
 
	// for each word we've been asked to say... 
	for (i  = CopyToken (words, 0, word, MAX_WORD_LEN);
		 i != DONE;
		 i  = CopyToken (words, i, word, MAX_WORD_LEN))
	{
		new output[MAX_TEXT_LENGTH];
 
		strinit (output);
 
		AddWordToStr (word, output, MAX_TEXT_LENGTH);
 
		len += strlen(output);
		if (len > MAX_EXEC_LEN)
		{
			selfprintf ("Setting timer at %i", previ);
 
			if (previ == 0)
			{
				selfprintf ("Sounds too long for '%s'!!", word);
				return;
			}
 
			set_timer ("YakTimer", 2, 1, words[previ]);
 
			break;
		}
 
		strcat (final, output, MAX_EXEC_LEN);
 
		previ = i;
	}
 
	SpeakAll (final);
}
 
AddWordToStr (word[], output[], maxlen)
{
	new i;
	new len;
	new line[MAX_TEXT_LENGTH];
	new lineNo;
 
	len = strlen (word);
	if (len == 0)
		return;
 
	for (i = 0; i < MAX_UNSAVED_WORDS; i++)
	{
		if (strlen(UnsavedWords[i]) == 0)
			continue;
 
		if (strcmp(UnsavedWords[i], word) == 0)
		{
			selfprintf ("Unsaved word: %s", word);
 
			AddSoundsToStr (UnsavedSounds[i], output, maxlen);
			return;
		}
	}
 
	for (lineNo  = ReadLine (WORDS_FILE, 0, line, MAX_TEXT_LENGTH);
		 lineNo != DONE;
		 lineNo  = ReadLine (WORDS_FILE, lineNo, line, MAX_TEXT_LENGTH))
	{
		new token[MAX_WORD_LEN];
 
		if (CopyToken (line, 0, token, MAX_WORD_LEN) == DONE)
			continue;
 
		if (strcmp (token, word) != 0)
		{
			continue;
		}
 
		i = SkipWS(line, len);
		if (i == DONE)
		{
			selfprintf ("Malformed syntax for '%s' in %s, line %i",
				word, WORDS_FILE, lineNo);
 
			return;
		}
 
		AddSoundsToStr (line[i], output, maxlen);
 
		return;
	}
 
	if (strlen(output) > 0)
		strcat (output, " ", maxlen);
 
	strcat (output, word, maxlen);
	strcat (output, " ", maxlen);
}
 
 
AddSoundsToStr (source[], output[], maxlen)
{
	new i;
	new t[MAX_TOKEN_LEN];
 
	if (strlen (source) == 0)
		return;
 
	for (i = CopyToken (source, 0, t, MAX_TOKEN_LEN); i != DONE;
		 i = CopyToken (source, i, t, MAX_TOKEN_LEN))
	{
		new found;
		new s;
 
		if (strlen (t) == 0)
			continue;
 
		found = 0;
 
		for (s = 0; s < NumSounds; s++)
		{
			if (strcmp (Tokens[s], t) != 0)
				continue;
 
			found = 1;
			strcat (output, Sounds[s], maxlen);
			strcat (output, " ", maxlen);
			break;
		}
 
		if (found == 0)
		{
			strcat (output, t, maxlen);
			strcat (output, " ", maxlen);
		}
	}
}
 
 
ReadSounds()
{
	new i;
	new token[MAX_TEXT_LENGTH];
	new lineNo;
	new line[MAX_TEXT_LENGTH];
 
	for (i = 0; i < MAX_SOUNDS; i++)
	{
		strinit (Sounds[i]);
		strinit (Tokens[i]);
		SavedSounds[i] = 0;
	}
 
	NumSounds = 0;
 
	for (lineNo  = ReadLine (SOUNDS_FILE, 0, line, MAX_TEXT_LENGTH);
		 lineNo != DONE && NumSounds < MAX_SOUNDS;
		 lineNo  = ReadLine (SOUNDS_FILE, lineNo, line, MAX_TEXT_LENGTH))
	{
		i = CopyToken (line, 0, token, MAX_TEXT_LENGTH);
		if (i == DONE)
			continue;
 
		// strip whitespace between token and sound
 
		i = SkipWS (line, i);
		if (i == DONE)
		{
			selfprintf ("Bogus syntax on line %i of %s", lineNo, SOUNDS_FILE);
			continue;
		}
 
		AddSound (token, line[i], 1);
	}
}
 
AddSound (key[], vox[], saved)
{
	new i;
 
	for (i = 0; i < MAX_SOUNDS; i++)
	{
		if (strlen(Tokens[i]) == 0)
		{
			strcpy (Tokens[i], key, MAX_TOKEN_LEN);
			strcpy (Sounds[i], vox, MAX_SOUND_LEN);
 
			SavedSounds[i] = saved;
 
			NumSounds++;
 
			return;
		}
 
		if (strcmp (Tokens[i], key) == 0)
		{
			selfprintf ("Duplicate sound '%s' found", key);
 
			strcpy (Tokens[i], key, MAX_TOKEN_LEN);
			strcpy (Sounds[i], vox, MAX_SOUND_LEN);
 
			SavedSounds[i] = saved;
 
			return;
		}
	}
 
	selfmessage ("Too many sounds!");
}
 
Speak (words[], HLUser)
{
	new output[MAX_TEXT_LENGTH + 10];
	new user[MAX_NAME_LENGTH];
 
	convert_string(HLUser, user, MAX_NAME_LENGTH);
 
	snprintf (output, MAX_TEXT_LENGTH, "speak ^"%s^"", words);
 
	execclient (user, output);
}
 
SpeakAll (words[])
{
	new output[MAX_TEXT_LENGTH + 10];
 
	snprintf (output, MAX_TEXT_LENGTH, "speak ^"%s^"", words);
 
	execclient_all (output);
}
 
 
SetSound (command[], name[], saved, HLUser)
{
	new i;
	new needUsage;
	new token[MAX_TEXT_LENGTH];
 
	needUsage = 0;
 
	if (strlen (command) > 0)
	{
		i = CopyToken (command, 0, token, MAX_TEXT_LENGTH);
		if (i != DONE)
		{
			i = SkipWS (command, i);
			if (i != DONE)
			{
				AddSound (token, command[i], saved);
 
				Speak (command[i], HLUser);
			}
			else
			{
				for (i = 0; i < NumSounds; i++)
				{
					if (strcmp (Tokens[i], command) != 0)
						continue;
 
					Speak (Sounds[i], HLUser);
 
					return 1;
				}
 
				selfmessage ("Sound not found!");
 
				needUsage = 1;
			}
		}
		else
			needUsage = 1;
	}
	else
		needUsage = 1;
 
	if (needUsage)
	{
		selfprintf ("Usage: %s %s <key> <sounds>", MY_NAME, name);
 
		return 1;
	}
 
	return 0;
}
 
WriteSound (command[], HLUser)
{
	if (SetSound(command, OPTION_ADD_SOUND, 1, HLUser) != 0)
		return;
 
	writefile (SOUNDS_FILE, command);
}
 
SetWord (command[], HLUser)
{
	new i;
	new j;
	new output[MAX_TEXT_LENGTH];
	new word[MAX_TEXT_LENGTH];
 
	i = CopyToken (command, 0, word, MAX_TEXT_LENGTH);
	if (i == DONE)
	{
		selfprintf ("%s %s <word> [<sounds>]", MY_NAME, OPTION_SET_WORD);
 
		return;
	}
 
	i = SkipWS (command, i);
	if (i == DONE)
	{
		// if they just gave the word without sounds, try to speak that word
 
		AddWordToStr (word, output, MAX_TEXT_LENGTH);
 
		Speak (output, HLUser);
 
		return;
	}
 
	AddSoundsToStr (command[i], output, MAX_TEXT_LENGTH);
 
	Speak (output, HLUser);
 
	for (j = 0; j < MAX_UNSAVED_WORDS; j++)
	{
		if (strlen (UnsavedWords[j]) == 0 || strcmp (word, UnsavedWords[j]) == 0)
		{
			strcpy (UnsavedWords[j], word, MAX_TEXT_LENGTH);
			strcpy (UnsavedSounds[j], command[i], MAX_TEXT_LENGTH);
			return;
		}
	}
 
	selfprintf ("Too many unsaved words! This one was not saved. Use %s %s", MY_NAME, OPTION_SAVE_WORDS);
}
 
WriteWord (command[], HLUser)
{
	SetWord (command, HLUser);
 
	writefile (WORDS_FILE, command);
}
 
SaveWords()
{
	new i;
	new line[MAX_TEXT_LENGTH];
 
	for (i = 0; i < MAX_UNSAVED_WORDS; i++)
	{
		if (strlen (UnsavedWords[i]) > 0)
		{
			snprintf (line, MAX_TEXT_LENGTH, "%s      %s^n", 
				UnsavedWords[i], UnsavedSounds[i]);
 
			writefile (WORDS_FILE, line);
 
			strinit (UnsavedWords[i]);
			strinit (UnsavedSounds[i]);
		}
	}
 
	selfmessage ("Done!");
}
 
SaveSounds()
{
	new i;
	new line[MAX_TEXT_LENGTH];
 
	for (i = 0; i < NumSounds; i++)
	{
		if (SavedSounds[i] != 0)
			continue;
 
		snprintf (line, MAX_TEXT_LENGTH, "%s      %s^n",
			Tokens[i], Sounds[i]);
 
		writefile (SOUNDS_FILE, line);
 
		SavedSounds[i] = 1;
	}
 
	selfmessage ("Done!");
}
 
FlushUnsaved()
{
	new i;
 
	NumSounds = 0;
	ReadSounds();
 
	for (i = 0; i < MAX_UNSAVED_WORDS; i++)
	{
		strinit (UnsavedWords[i]);
		strinit (UnsavedSounds[i]);
	}
 
	selfmessage ("Done!");
}
 
ListSounds (match[])
{
	new i;
	new output[MAX_TEXT_LENGTH];
 
	strinit (output);
 
	for (i = 0; i < NumSounds; i++)
	{
		if (match[0] != NULL_CHAR && strcasestr (Tokens[i], match) == DONE)
			continue;
 
		if (SavedSounds[i] == 0)
			AddOutput (Tokens[i], output, 0, 1);
		else
			AddOutput (Tokens[i], output, 0, 0);
	}
 
	selfmessage (output);
}
 
ListWords (lineStr[])
{
	new count;
	new line[MAX_TEXT_LENGTH];
	new lineNo;
	new output[MAX_TEXT_LENGTH];
	new start;
	new word[MAX_TEXT_LENGTH];
 
	strinit (output);
 
	if (lineStr[0] == NULL_CHAR)
		start = 1;
	else
		start = strtonum (lineStr);
 
	count = 0;
 
	for (lineNo  = ReadLine (WORDS_FILE, start, line, MAX_TEXT_LENGTH);
		 lineNo != DONE && count < MAX_OUT_LINES;
		 lineNo =  ReadLine (WORDS_FILE, lineNo, line, MAX_TEXT_LENGTH))
	{
		if (CopyToken (line, 0, word, MAX_TEXT_LENGTH) == DONE)
			continue;
 
		count = AddOutput (word, output, count);
	}
 
	selfmessage (output);
 
	if (count >= MAX_OUT_LINES)
	{
		selfprintf ("Use %s %s %i for the next set of words", MY_NAME, OPTION_WORDS, lineNo);
	}
}
 
ListVox (lineStr[])
{
	new count;
	new line[MAX_TEXT_LENGTH];
	new lineNo;
	new output[MAX_TEXT_LENGTH];
	new start;
 
	strinit (output);
 
	if (lineStr[0] == NULL_CHAR)
		start = 1;
	else
		start = strtonum (lineStr);
 
	count = 0;
 
	for (lineNo  = ReadLine (VOX_WORDS_FILE, start, line, MAX_TEXT_LENGTH);
		 lineNo != DONE && count < MAX_OUT_LINES;
		 lineNo  = ReadLine (VOX_WORDS_FILE, lineNo, line, MAX_TEXT_LENGTH))
	{
		count = AddOutput (line, output, count);
	}
 
	selfmessage (output);
 
	if (count >= MAX_OUT_LINES)
	{
		selfprintf ("Use %s %s %i for the next set of words", MY_NAME, OPTION_VOX, lineNo);
	}
}
 
MatchWords (match[])
{
	new count;
	new line[MAX_TEXT_LENGTH];
	new lineNo;
	new hadHit;
	new output[MAX_TEXT_LENGTH];
	new token[MAX_TEXT_LENGTH];
 
	if (strlen (match) == 0)
	{
		selfprintf ("You must supply a string to match on. EG, '%s %s blue'", MY_NAME, OPTION_MATCH);
		selfprintf ("will display all words with 'blue' in them.");
 
		return;
	}
 
	hadHit = 0;
	count = 0;
 
	// check lines file
 
	hadHit = 0;
 
	for (lineNo  = ReadLine (LINES_FILE, 0, line, MAX_TEXT_LENGTH);
		 lineNo != DONE && count < MAX_OUT_LINES;
		 lineNo  = ReadLine (LINES_FILE, lineNo, line, MAX_TEXT_LENGTH))
	{
		if (CopyToken (line, 0, token, MAX_TEXT_LENGTH) == DONE)
			continue;
 
		// the above check was just to make sure the line kinda sorta looks
		// right. We actually want to match on the whole line..
 
		if (strcasestr (line, match) == -1)
			continue;
 
		if (hadHit == 0)
		{
			hadHit = 1;
 
			selfprintf ("Matches from %s:", LINES_FILE);
 
			count++;
		}
 
		selfmessage (line);
 
		if (++count > MAX_OUT_LINES)
		{
			selfmessage ("Too many matches. Stopping. Please be more specific.");
			return;
		}
	}
 
	if (hadHit != 0)
		selfmessage ("");
 
	if (count < MAX_UNSAVED_WORDS)
	{
		strinit (output);
		hadHit = 0;
 
		// check unsaved words..
 
		for (lineNo = 0; lineNo < MAX_UNSAVED_WORDS; lineNo++)
		{
			if (strlen(UnsavedWords[lineNo]) == 0)
				continue;
 
			if (hadHit == 0)
			{
				hadHit = 1;
 
				selfmessage ("Unsaved words:");
				count++;
			}
 
			count = AddOutput (UnsavedWords[lineNo], output, count, 1);
		}
 
		if (strlen (output) > 0)
		{
			selfmessage (output);
			strinit (output);
		}
	}
 
 
	// next, check for matches in the words file..
 
	if (count < MAX_UNSAVED_WORDS)
	{
		if (hadHit != 0)
			selfmessage ("");
 
		hadHit = 0;
		strinit (output);
 
		for (lineNo  = ReadLine (WORDS_FILE, 0, line, MAX_TEXT_LENGTH);
			 lineNo != DONE && count < MAX_OUT_LINES;
			 lineNo  = ReadLine (WORDS_FILE, lineNo, line, MAX_TEXT_LENGTH))
		{
			if (CopyToken (line, 0, token, MAX_TEXT_LENGTH) == DONE)
				continue;
 
			if (strcasestr (token, match) == -1)
				continue;
 
			if (hadHit == 0)
			{
				hadHit = 1;
 
				selfprintf ("Matches from %s:", WORDS_FILE);
 
				strinit (output);
 
				count++;
			}
 
			count = AddOutput (token, output, count);
		}
 
		if (strlen(output) > 0)
		{
			count++;
 
			if (count < MAX_OUT_LINES)
				selfmessage (output);
		}
	}
 
 
	// last, check to see if there's any built-in matches
 
	if (count < MAX_OUT_LINES)
	{
		if (hadHit != 0)
			selfmessage ("");
 
		hadHit = 0;
		strinit (output);
 
		for (lineNo  = ReadLine (VOX_WORDS_FILE, 0, line, MAX_TEXT_LENGTH);
			 lineNo != DONE && count < MAX_OUT_LINES;
			 lineNo  = ReadLine (VOX_WORDS_FILE, lineNo, line, MAX_TEXT_LENGTH))
		{
			if (strcasestr (line, match) == DONE)
				continue;
 
			if (hadHit == 0)
			{
				hadHit = 1;
 
				selfprintf ("Matches from %s:", VOX_WORDS_FILE);
 
				strinit (output);
 
				count++;
			}
 
			count = AddOutput (line, output, count);
		}
 
		if (strlen(output) > 0)
		{
			count++;
			if (count < MAX_OUT_LINES)
				selfmessage (output);
		}
	}
 
	if (count >= MAX_OUT_LINES)
	{
		selfprintf ("Too many matches. Stopping. Please be more specific.");
		return;
	}
}
 
DumpUnsaved()
{
	new count;
	new i;
	new hadHit;
 
	count = 0;
	hadHit = 0;
 
	for (i = 0; i < NumSounds && count < MAX_OUT_LINES; i++)
	{
		if (SavedSounds[i] != 0 || strlen(Tokens[i]) == 0)
			continue;
 
		if (hadHit == 0)
		{
			hadHit = 1;
			selfmessage ("Unsaved sounds:");
 
			count++;
		}
 
		selfprintf ("^t%s^t^t%s", Tokens[i], Sounds[i]);
		count++;
	}
 
	hadHit = 0;
 
	for (i = 0; i < MAX_UNSAVED_WORDS && count < MAX_OUT_LINES; i++)
	{
		if (strlen (UnsavedWords[i]) == 0)
			continue;
 
		if (hadHit == 0)
		{
			hadHit = 1;
			selfmessage ("Unsaved words:");
 
			count++;
		}
 
		selfprintf ("^t%s^t^t%s", UnsavedWords[i], UnsavedSounds[i]);
		count++;
	}
 
	if (count >= MAX_OUT_LINES)
	{
		selfmessage ("Oops: too many unsaved sounds and/or words!");
	}
}
 
 
AddOutput (str[], line[], count, flag = 0)
{
	if (strlen(str) + strlen(line) > MAX_COLUMN)
	{
		selfmessage (line);
		strinit (line);
		count++;
	}
 
	new tmp[MAX_TEXT_LENGTH];
 
	if (flag)
		snprintf (tmp, MAX_TEXT_LENGTH, "^t^t*%s", str);
	else
		snprintf (tmp, MAX_TEXT_LENGTH, "^t^t %s", str);
 
	strcat (line, tmp, MAX_TEXT_LENGTH);
 
	return count;
}
 
 
ReadLine (file[], lineNo, buffer[], maxlen)
{
	new done;
	new i;
	new ret;
	new raw[MAX_TEXT_LENGTH];
 
	done = 0;
 
	while (!done)
	{
		ret = readfile (file, raw, lineNo, MAX_TEXT_LENGTH);
		if (ret == 0)
		{
			// EOF
 
			return DONE;
		}
 
		i = SkipWS (raw);
		if (i == DONE || strncmp (raw, "//", 2) == 0)
			lineNo++;
		else
			done = 1;
	}
 
	strncpy (buffer, raw[i], maxlen, MAX_TEXT_LENGTH);
 
	// return next lineNo to read
 
	return lineNo + 1;
}
 
selfprintf (origformat[], ...)
{
	new i;
	new format[MAX_TEXT_LENGTH];
	new output[MAX_TEXT_LENGTH];
 
	if (numargs() == 1)
	{
		selfmessage (origformat);
		return;
	}
 
	strcpy (format, origformat, MAX_TEXT_LENGTH);
 
	for (i = 1; i < numargs(); i++)
	{
		if (getarg(i, 1) == 0)
		{
			// 2nd element is a 0 - assume it's a number
 
			snprintf (output, MAX_TEXT_LENGTH, format, getarg(i));
		}
		else
		{
			new tmp[MAX_TEXT_LENGTH];
			new p;
 
			// element 1 is not 0.. assume this is a string
 
			strinit (tmp);
 
			for (p = 0; getarg(i, p) != NULL_CHAR && p < MAX_TEXT_LENGTH; p++)
			{
				tmp[p] = getarg(i, p);
			}
 
			tmp[p] = NULL_CHAR;
 
			snprintf (output, MAX_TEXT_LENGTH, format, tmp);
		}
 
		strcpy (format, output, MAX_TEXT_LENGTH);
	}
 
	selfmessage (output);
}
 
CopyToken (source[], start, token[], maxlen)
{
	new i;
	new j;
 
	strinit (token);
 
	i = SkipWS (source, start);
	if (i == DONE)
		return DONE;
 
	for (j = 0; source[i] > SPACE && j < maxlen - 1; i++)
	{
		token[j++] = source[i];
	}
 
	token[j] = NULL_CHAR;
 
	return i;
}
 
SkipWS (source[], start = 0)
{
	new i;
 
	if (start == DONE)
		return DONE;
 
	for (i = start; source[i] != NULL_CHAR; i++)
	{
		if (source[i] > SPACE)
		{
			return i;
		}
	}
 
	return DONE;
}