/*----------------------------------------------------------------------------*\
					===========================
					Y Sever Includes - INI Core
					===========================
Description:
	Does some internal processing for the text system.
Legal:
	Version: MPL 1.1
	
	The contents of this file are subject to the Mozilla Public License Version 
	1.1 (the "License"); you may not use this file except in compliance with 
	the License. You may obtain a copy of the License at 
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS" basis,
	WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
	for the specific language governing rights and limitations under the
	License.
	
	The Original Code is the YSI ini include.
	
	The Initial Developer of the Original Code is Alex "Y_Less" Cole.
	Portions created by the Initial Developer are Copyright (C) 2011
	the Initial Developer. All Rights Reserved.
	
	Contributors:
		ZeeX, koolk, JoeBullet/Google63, g_aSlice/Slice
	
	Thanks:
		JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL.
		ZeeX - Very productive conversations.
		koolk - IsPlayerinAreaEx code.
		TheAlpha - Danish translation.
		breadfish - German translation.
		Fireburn - Dutch translation.
		yom - French translation.
		50p - Polish translation.
		Zamaroht - Spanish translation.
		Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes
			for me to strive to better.
		Pixels^ - Running XScripters where the idea was born.
		Matite - Pestering me to release it and using it.
	
	Very special thanks to:
		Thiadmer - PAWN, whose limits continue to amaze me!
		Kye/Kalcor - SA:MP.
		SA:MP Team past, present and future - SA:MP.
	
Version:
	1.0
Changelog:
	01/05/11:
		First version.
Functions:
	Public:
		-
	Core:
		-
	Stock:
		-
	Static:
		-
	Inline:
		-
	API:
		-
Callbacks:
	-
Definitions:
	-
Enums:
	-
Macros:
	-
Tags:
	-
Variables:
	Global:
		-
	Static:
		-
Commands:
	-
Compile options:
	-
Operators:
	-
\*----------------------------------------------------------------------------*/

#include <YSI\y_amx>
#include <YSI\y_utils>
#include <YSI\y_ini>
#include <YSI\y_debug>
#include <YSI\y_iterate>

#include <YSI\y_scriptinit>

#include <YSI\y_languages>

#include <YSI\y_dialog>

#include <YSI\y_inline>

enum e_TEXT_DISPLAY_TYPE
{
	text_type_print    = cellmin,
	text_type_3d       = -4,
	text_type_player   = -3,
	text_type_alert    = -2,
	text_type_td       = -1,
	text_type_client   = 0,
	text_type_game1,
	text_type_game2,
	text_type_game3,
	text_type_game4,
	text_type_game5,
	text_type_game6
}

//#include <YSI\y_master>

// End conditions for the recursive calls (strings and publics).
#define _YT@LE@E%0>
#define _YT@LT@E%0> ;

// Recursive wanted text definition.  Needs two levels of indirection to strip
// the excess commas (,%0,%1).
#if defined Y_TEXT_UNIQUE
	#define _YT@LO(,%0,%1) UNIQUE_FUNCTION<%0@%1@...@yX_>();public UNIQUE_FUNCTION<%0@%1@...@yX_>(){}
#else
	#define _YT@LO(,%0,%1) %0@%1@yX_();public %0@%1@yX_(){}
#endif
#define _YT@LE%0[%1]%2> _YT@LO(%0,%1) _YT@LE%2>

// Recursive local default string definition.
#define _YT@LJ(,%0,%1) #%0":"#%1"|"
#define _YT@LT%0[%1]%2> _YT@LJ(%0,%1)_YT@LT%2>

// Entry point for the loaders.
#define text%0[%1]%2; _YT@LE,%0[%1]%2@E>static stock DEFAULT_TEXT_SET[]=_YT@LT,%0[%1]%2@E>

#define Text_RegisterTag(%1) \
	text core[%1]

// Clever macros to load default values when not specified.
//#define Text_Send(%0,%1) _Text_Send(%0,DEFAULT_TEXT_SET,#%1)

#define DO_TEXT_SET: _:HAS_TEXT_SET:NO_TEXT_SET:

#define HAS_TEXT_SET:NO_TEXT_SET:%0$%1||| DEFAULT_TEXT_SET,#%1|||

#define NO_TEXT_SET: "\3",

#define DEFAULT_TEXT_SET,#%1->%2||| #core#:#%1#|,#%2|||

// WTF does this do?  I can't even remember what slots are!
//#define core":"#%1:%2"|",%3) %1":"#%2"|",%3)
#define core#:#%1[%2]%9#|,%3) %1#:#%2#|,%3)

/*#if defined _inc_y_languages
	#define Text_GetLanguageCodes() Langs_GetLanguageCodes()
	
	#define Text_GetPlayerLanguage(%0) Langs_GetPlayerLanguage(%0)
#else
	// y_languages doesn't really do much TBH...  This used to have three
	// langugages, but then I realised that without the "y_languages"
	// library there was no way for a player to select their language.
	// "XX" is used as a dummy to ensure that the array in the enum is always
	// valid and that the compiler doesn't optimise it out.
	#define Text_GetLanguageCodes() "EN|XX|" //|NL|ES|"
	// Number of "|" separated strings above (always 1).
	#define YSI_MAX_LANGUAGES (Language:2)
	
	#define Text_GetPlayerLanguage(%0) (Language:0)
#endif*/

// Return types
enum E_TEXT_FIND
{
	E_TEXT_FIND_NONE,
	E_TEXT_FIND_JUST,
	E_TEXT_FIND_MANY
}

// This holds the data for all entry points.
enum e_TEXT_SETS
{
	e_TEXT_SETS_NAME[28],
	e_TEXT_SETS_HASH,
	e_TEXT_SETS_INDEX,
	e_TEXT_SETS_LOADED
}

enum e_TEXT_ENTRY
{
	e_TEXT_ENTRY_NAME[MAX_INI_ENTRY_NAME],// char],
	e_TEXT_ENTRY_HASH,
	e_TEXT_ENTRY_LEFT,
	e_TEXT_ENTRY_RIGHT,
	e_TEXT_ENTRY_POINTERS[YSI_MAX_LANGUAGES]
}

#define TEXT_MASTER YSI_g_sDistributionID

#include <YSI\y_styles>
#include <YSI\y_hooks>

static stock
	YSI_g_sTextSets[Y_TEXT_MAX_SETS][e_TEXT_SETS],
	YSI_g_sTextEntries[MAX_TEXT_ENTRIES][e_TEXT_ENTRY],
	YSI_g_sTextStrings[MAX_TEXT_ENTRIES * _:YSI_MAX_LANGUAGES][MAX_INI_ENTRY_TEXT],// char],
	YSI_g_sReturnText[MAX_INI_ENTRY_TEXT],
	YSI_g_sExtraText[MAX_INI_ENTRY_TEXT],
	YSI_g_sUnusedEntry = 0,
	YSI_g_sDistributionID = -1,
	YSI_g_sUnusedSlot = 0,
	YSI_g_sCurLoadTextFile[64],
	Language:YSI_g_sLangIndex;

// OnFilterScriptInit

// =============================================================================
// | ========================================================================= |
// | |                                                                       | |
// | |                   HERE STARTS THE LOADING CODE                        | |
// | |                                                                       | |
// | ========================================================================= |
// =============================================================================

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

// This callback is hard coded in to YSI to be called absolutely last!
forward Text_SpecialInit();

public Text_SpecialInit()
{
	P:1("Text_SpecialInit called");
	// Scan the AMX for all public functions of the type "XX@YY@NN@yX_".  These
	// are the declarations for included texts.
	Text_RefreshLoaded();
	_Styles_SpecialInit();
	P:2("Text_SpecialInit ended");
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

//static stock Text_GetDistributionID()
hook OnScriptInit()
{
	P:1("Text_OnScriptInit called");
	// Initialise.
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		YSI_g_sTextSets[i][e_TEXT_SETS_NAME][0] = '\1';
		YSI_g_sTextSets[i][e_TEXT_SETS_INDEX] = -1;
	}
	for (new i = 0; i != MAX_TEXT_ENTRIES; ++i)
	{
		YSI_g_sTextEntries[i][e_TEXT_ENTRY_HASH] = i + 1;
	}
	for (new i = 0; i != MAX_TEXT_ENTRIES * _:YSI_MAX_LANGUAGES; ++i)
	{
		YSI_g_sTextStrings[i][0] = i + 1;
	}
	new
		masters = getproperty(8, YSIM_TEXT_D),
		i = 0;
	while (i != 32)
	{
		if (!(masters & (1 << i)))
		{
			YSI_g_sDistributionID = i;
			masters |= 1 << i;
			break;
		}
		++i;
	}
	if (i != 32)
	{
		setproperty(8, YSIM_TEXT_D, masters);
	}
	P:C(else P:E("y_text distribution ID not set"););
	CallRemoteFunction("Text_ResetLoaded", "");
	state y_text_ending : n;
	state y_text_get_text : n;
	state y_render_show : y_render_show_print;
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

hook OnScriptExit()
{
	new
		idx = 0,
		buffer[32],
		pos;
	while ((idx = AMX_GetPublicNameSuffix(idx, buffer, _A<@yX_>)))
	{
		pos = chrfind('@', buffer);
		if (pos != -1)
		{
			buffer[pos] = ':';
			pos = chrfind('@', buffer, pos);
			if (pos != -1)
			{
				buffer[pos] = '\0';
				if (existproperty(7, buffer) && getproperty(7, buffer) == TEXT_MASTER)
				{
					deleteproperty(7, buffer);
				}
			}
		}
	}
	state y_text_ending:y;
	CallRemoteFunction("Text_RefreshLoaded", "");
}

/*----------------------------------------------------------------------------*\
Function:
	Text_ResetLoaded
Params:
	-
Return:
	-
Notes:
	When a mode ends this is called so that other scripts can know to re-load
	any text which was previously owned by this script that they still need.
\*----------------------------------------------------------------------------*/

forward Text_ResetLoaded();

public Text_ResetLoaded() <>
{
}

public Text_ResetLoaded() <y_text_ending:n>
{
	// Called to reset the owners of texts after a gamemode restart.
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		if (YSI_g_sTextSets[i][e_TEXT_SETS_NAME][0] != '\1')
		{
			setproperty(7, YSI_g_sTextSets[i][e_TEXT_SETS_NAME], TEXT_MASTER);
		}
	}
}

/*----------------------------------------------------------------------------*\
Function:
	Text_RefreshLoaded
Params:
	-
Return:
	-
Notes:
	Loops through all text definition functions in the mode (defined as:
	"file@section@unique@yX_" (note that "@unique" is optional but irrelevant).
	If any are found and the property "file:section" isn't defined claims
	ownership of that section so that the text can be loaded in to this mode.
\*----------------------------------------------------------------------------*/

forward Text_RefreshLoaded();

public Text_RefreshLoaded() <y_text_ending:y>
{
	//printf("Text_RefreshLoaded: y");
	// Do nothing, only called for other scripts to check that all their text
	// items are still loaded.
}

public Text_RefreshLoaded() <y_text_ending:n>
{
	//printf("Text_RefreshLoaded: n");
	if (Langs_GetLanguageCount() == Language:0)
	{
		P:E("No languages found - Did you add \"Langs_AddLanguage\" to the script init (NOT \"main\")?");
	}
	new
		idx = 0,
		buffer[32],
		pos;
	while ((idx = AMX_GetPublicNameSuffix(idx, buffer, _A<@yX_>)))
	{
		strunpack(buffer, buffer);
		pos = chrfind('@', buffer);
		//printf("%d, %x", pos, buffer[0]);
		if (pos != -1)
		{
			// Get rid of the end parts, doesn't matter if "Y_TEXT_UNIQUE" is
			// defined or not as EVERYTHING later is dropped.
			buffer[pos] = ':';
			pos = chrfind('@', buffer, pos);
			if (pos != -1)
			{
				buffer[pos] = '\0';
				//printf("buffer: %s %d", buffer, existproperty(7, buffer));
				//buffer[pos + 1] = '\0';
				if (!existproperty(7, buffer))
				{
					setproperty(7, buffer, TEXT_MASTER);
					Text_AddLocal(buffer, bernstein(buffer));
				}
			}
		}
	}
	Text_LoadLocals();
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

static stock Text_AddLocal(buffer[], hash)
{
	P:4("Text_AddLocal called: \"%s\", %i", buffer, hash);
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		if (YSI_g_sTextSets[i][e_TEXT_SETS_NAME][0] == '\1')
		{
			YSI_g_sTextSets[i][e_TEXT_SETS_HASH] = hash;
			strcpy(YSI_g_sTextSets[i][e_TEXT_SETS_NAME], buffer, 28);
			return;
		}
	}
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

forward Text_INILoad(offset, tag[], name[], value[]);

public Text_INILoad(offset, tag[], name[], value[])
{
	// Get the path.
	// Get the file.
	// Get the extension.
	P:1("Text_INILoad called: %d, %s, %s, %s", offset, tag, name, value);
	P:5("Text_INILoad: IsPacked? %08x%08x", value[0], value[1]);
	new
		fname[sizeof (YSI_g_sCurLoadTextFile) - (3 + 9)],
		len = strlen(YSI_g_sCurLoadTextFile);
	//format(fname, sizeof (fname
	strcpy(fname, YSI_g_sCurLoadTextFile[9], len - (3 + 9 - 1));
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		//printf("fname: %s, %d, %d, %s", fname, len, offset, YSI_g_sTextSets[i][e_TEXT_SETS_NAME]);
		if (!strcmp(YSI_g_sTextSets[i][e_TEXT_SETS_NAME], fname, true, offset - 1) && !strcmp(YSI_g_sTextSets[i][e_TEXT_SETS_NAME][offset], tag, true))
		{
			//printf("%d \"%s\" \"%s\" \"%s\"", len, fname, YSI_g_sCurLoadTextFile[len - 2], tag);
			len = strlen(name);
			new
				slot = name[--len],
				number,
				hash = 1;
			P:6("Text_INILoad: Get Number");
			while ('0' <= slot <= '9')
			{
				name[len] = '\0';
				number += (slot - '0') * hash;
				hash *= 10;
				slot = name[--len];
			}
			// So "MY_TEXT_1" becomes "MY_TEXT", not "MY_TEXT_".
			if (slot == '_')
			{
				name[len] = '\0';
			}
			hash = bernstein(name);
			slot = Text_FindEntry(YSI_g_sTextSets[i][e_TEXT_SETS_INDEX], hash);
			P:6("Text_INILoad: Check slot %d", slot);
			if (slot == -1)
			{
				Text_AddEntry(i, name, value, hash);
				//P:5("%d,%d,%d,%d,%d", YSI_g_sTextStrings[ss][0], YSI_g_sTextStrings[ss][1], YSI_g_sTextStrings[ss][2], YSI_g_sTextStrings[ss][3], YSI_g_sTextStrings[ss][4]);
			}
			else
			{
				P:6("Text_INILoad: Check Collision");
				if (strcmp(name, YSI_g_sTextEntries[slot][e_TEXT_ENTRY_NAME], true))
				{
					P:E("Text collision on \"%s\" and \"%s\"!", name, YSI_g_sTextSets[i][e_TEXT_SETS_NAME]);
					return;
				}
				else if (YSI_g_sTextEntries[slot][e_TEXT_ENTRY_POINTERS][YSI_g_sLangIndex] == -1)
				{
					new
						ss = Text_GetFreeSlot();
					if (ss == -1)
					{
						P:E("Text buffer full!");
						return;
					}
					//strpack(YSI_g_sTextStrings[ss], value, MAX_INI_ENTRY_TEXT * 4);
					Format_Standardise(value, YSI_g_sTextStrings[ss], MAX_INI_ENTRY_TEXT);
					P:5("%d,%d,%d,%d,%d", YSI_g_sTextStrings[ss][0], YSI_g_sTextStrings[ss][1], YSI_g_sTextStrings[ss][2], YSI_g_sTextStrings[ss][3], YSI_g_sTextStrings[ss][4]);
					//strcpy(YSI_g_sTextStrings[ss], value, MAX_INI_ENTRY_TEXT * 4);
					YSI_g_sTextEntries[slot][e_TEXT_ENTRY_POINTERS][YSI_g_sLangIndex] = ss;
				}
				else if (number)
				{
					// Found a string with multiple entries in the INI file to
					// be linked together dynamically.
					new
						ss = Text_GetFreeSlot();
					if (ss == -1)
					{
						P:E("Text buffer full!");
						return;
					}
					Format_Standardise(value, YSI_g_sTextStrings[ss], MAX_INI_ENTRY_TEXT);
					hash = YSI_g_sTextEntries[slot][e_TEXT_ENTRY_POINTERS][YSI_g_sLangIndex];
					if (hash == -1)
					{
						// Will happen when the text entry exists in other
						// languages but not yet in this one.
						YSI_g_sTextEntries[slot][e_TEXT_ENTRY_POINTERS][YSI_g_sLangIndex] = ss;
					}
					else if (number == 1)
					{
						YSI_g_sTextEntries[slot][e_TEXT_ENTRY_POINTERS][YSI_g_sLangIndex] = ss;
						len = strlen(YSI_g_sTextStrings[ss]);
						if (len < sizeof (YSI_g_sTextStrings[]) - 2)
						{
							// Add the linked list.
							YSI_g_sTextStrings[ss][len] = '\05';
							YSI_g_sTextStrings[ss][len + 1] = hash;
							YSI_g_sTextStrings[ss][len + 2] = '\0';
						}
						else
						{
							P:W("Text size error on \"%s\"!", name);
						}
					}
					else
					{
						// Add the string to the chain.
						slot = 2;
						len = strlen(YSI_g_sTextStrings[hash]);
						// Loop through existing strings in this list till we
						// find the right position (either the specified ID or
						// the last item) at which to add the new string.
						while (slot < number)
						{
							if (len < 2)
							{
								break;
							}
							if (YSI_g_sTextStrings[hash][len - 2] != '\05')
							{
								break;
							}
							hash = YSI_g_sTextStrings[hash][len - 1];
							len = strlen(YSI_g_sTextStrings[hash]);
							++slot;
						}
						if (len < 2)
						{
							YSI_g_sTextStrings[hash][len] = '\05';
							YSI_g_sTextStrings[hash][len + 1] = ss;
							YSI_g_sTextStrings[hash][len + 2] = '\0';
						}
						else if (YSI_g_sTextStrings[hash][len - 2] == '\05')
						{
							slot = YSI_g_sTextStrings[hash][len - 1];
							YSI_g_sTextStrings[hash][len - 1] = ss;
							len = strlen(YSI_g_sTextStrings[ss]);
							if (len < sizeof (YSI_g_sTextStrings[]) - 2)
							{
								// Add the linked list.
								YSI_g_sTextStrings[ss][len] = '\05';
								YSI_g_sTextStrings[ss][len + 1] = slot;
								YSI_g_sTextStrings[ss][len + 2] = '\0';
							}
							else
							{
								P:W("Text size error on \"%s\"!", name);
							}
						}
						else
						{
							if (len < sizeof (YSI_g_sTextStrings[]) - 2)
							{
								// Add the linked list.
								YSI_g_sTextStrings[hash][len] = '\05';
								YSI_g_sTextStrings[hash][len + 1] = ss;
								YSI_g_sTextStrings[hash][len + 2] = '\0';
							}
							else
							{
								P:W("Text size error on \"%s\"!", name);
							}
						}
					}
				}
				else
				{
					P:W("Text repeat on \"%s\"!", name);
				}
			}
			// Add the current value to the current tag.
			break;
		}
	}
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

static stock Text_LoadLocals()
{
	//printf("Text_LoadLocals called");
	P:4("Text_LoadLocals called");
	// This is the interesting part.  Load all the sets which have a name not
	// '\1' and an index of -1.
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		P:6("Text_LoadLocals: i = %d, text = %s, %d, %d", i, YSI_g_sTextSets[i][e_TEXT_SETS_NAME], YSI_g_sTextSets[i][e_TEXT_SETS_INDEX], YSI_g_sTextSets[i][e_TEXT_SETS_HASH]);
		if (YSI_g_sTextSets[i][e_TEXT_SETS_INDEX] == -1 && YSI_g_sTextSets[i][e_TEXT_SETS_HASH])
		{
			new
				offset = chrfind(':', YSI_g_sTextSets[i][e_TEXT_SETS_NAME]) + 1,
				j = i + 1;
			while (j != Y_TEXT_MAX_SETS)
			{
				if (YSI_g_sTextSets[j][e_TEXT_SETS_INDEX] == -1 && YSI_g_sTextSets[j][e_TEXT_SETS_HASH] && !strcmp(YSI_g_sTextSets[i][e_TEXT_SETS_NAME], YSI_g_sTextSets[j][e_TEXT_SETS_NAME], false, offset))
				{
					break;
				}
				++j;
			}
			// Only load a file one.
			if (j != Y_TEXT_MAX_SETS)
			{
				continue;
			}
			--offset;
			//YSI_g_sTextSets[i][e_TEXT_SETS_NAME][offset] = '\0';
			YSI_g_sLangIndex = Language:0;
			new
				langs[YSI_MAX_STRING],
				pos;
			langs = Langs_GetLanguageCodes();
			while ((pos = chrfind('|', langs, pos)) != -1)
			{
				format(YSI_g_sCurLoadTextFile, sizeof (YSI_g_sCurLoadTextFile), "YSI/text/%.*s.%.2s", offset, YSI_g_sTextSets[i][e_TEXT_SETS_NAME], langs[pos - 2]);
				INI_ParseFile(YSI_g_sCurLoadTextFile, "Text_INILoad", _, true, offset + 1, _, true);
				++pos;
				++YSI_g_sLangIndex;
				// We can add code here to load the data from y_styles.
			}
			//format(YSI_g_sCurLoadTextFile, sizeof (YSI_g_sCurLoadTextFile), "%.*s", offset, YSI_g_sTextSets[i][e_TEXT_SETS_NAME]);
			P:6("Text_LoadLocals: Loading style %s (%d)", YSI_g_sCurLoadTextFile, offset);
			//_Styles_ParseOne(YSI_g_sCurLoadTextFile);
			//YSI_g_sTextSets[i][e_TEXT_SETS_NAME][offset] = ':';
		}
	}
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

forward Text_CheckLoad(buffer[], hash);

public Text_CheckLoad(buffer[], hash) <y_text_ending:y>
{
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		YSI_g_sTextSets[i][e_TEXT_SETS_HASH] = 0;
		YSI_g_sTextSets[i][e_TEXT_SETS_NAME][0] = '\1';
		YSI_g_sTextSets[i][e_TEXT_SETS_NAME][1] = '\0';
	}
}

public Text_CheckLoad(buffer[], hash) <y_text_ending:n>
{
	// Check if another script is already in charge of this set.
	if (getproperty(8, "T_CL"))
	{
		return;
	}
	// Nope, are we?
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		if (hash == YSI_g_sTextSets[i][e_TEXT_SETS_HASH] && !strcmp(buffer, YSI_g_sTextSets[i][e_TEXT_SETS_NAME]))
		{
			setproperty(8, "T_CL", 1);
			return;
		}
	}
}

/*----------------------------------------------------------------------------*\
Function:
	Text_ReturnTheText
Params:
	master - The script which owns this string.
	index - The slot in which this string is stored.
	str[] - The string to display.
	len0 - The length of the string array.
	style[E_STYLE_DATA] - The style of the string.
	len1 - The length of the style array.
	label[E_3D_DATA] - The 3D label style of the string.
	len2 - The length of the 3D array.
Return:
	-
Notes:
	This function is used to pass data from the owner script to the script
	which is doing the display.  It passes the string to format (as an array,
	not as a string, to keep the non-standard characters).  A copy of the style
	data, any 3D text label style data, the index and the master ID.  Note that
	TD data isn't handled here - if there is need to use a text draw the script
	will have to call to the remote script for display functions.  TD data is
	loaded in all scripts where it is relevant.  This does mean that if people
	modify a style we need to modify it in ALL scripts.
	
	TODO: Modify the y_text system to store copies of required strings in all
	modes, just because it's slightly better.
\*----------------------------------------------------------------------------*/

forward Text_ReturnTheText(master, index, str[], len0, style[E_STYLE_DATA], len1, label[E_3D_DATA], len2);

public Text_ReturnTheText(master, index, str[], len0, style[E_STYLE_DATA], len1, label[E_3D_DATA], len2) <y_text_get_text:n>
{
}

public Text_ReturnTheText(master, index, str[], len0, style[E_STYLE_DATA], len1, label[E_3D_DATA], len2) <y_text_get_text:y>
{
	P:4("Text_ReturnTheText called: %d,%d,%d,%d,%d len = %d (%s)", str[0], str[1], str[2], str[3], str[4], len0, str);
	strcpy(YSI_g_sReturnText, str, len0);
	setproperty(8, YSIM_TXTLEN, _Format_SetStyleData(master, index, style, label));
}

/*----------------------------------------------------------------------------*\
Function:
	_Text_CheckOwnership
Params:
	file[] - File to check.
	str[] - Section name to check.
Return:
	Does this script own the named section in the current file?
Notes:
	Sets "YSI_g_sReturnText" for use in "_Text_LookupName" too.
\*----------------------------------------------------------------------------*/

stock _Text_CheckOwnership(file[], str[])
{
	P:4("_Text_CheckOwnership called: %s, %s", file, str);
	format(YSI_g_sReturnText, sizeof (YSI_g_sReturnText), "%s:%s", file, str);
	return existproperty(7, YSI_g_sReturnText) && (getproperty(7, YSI_g_sReturnText) == TEXT_MASTER);
}

/*----------------------------------------------------------------------------*\
Function:
	_Text_LookupName
Params:
	name[] - Name of the string to look up.
Return:
	Slot storing the pointers for the named section.
Notes:
	Assumes this script owns the section based on previously having called
	"_Text_CheckOwnership".  This is assumed as both functions are called from
	only y_styles (hence the private naming convention).  Note that the string
	"YSI_g_sReturnText" is set in "_Text_CheckOwnership" because we make
	guarantees about the order in which these functions are called.
\*----------------------------------------------------------------------------*/

stock _Text_LookupName(name[])
{
	P:4("_Text_LookupName called: %s, %s", name, YSI_g_sReturnText);
	//format(YSI_g_sReturnText, sizeof (YSI_g_sReturnText), "%s:%s", YSI_g_sCurLoadTextFile, str);
	new
		sh = bernstein(YSI_g_sReturnText);
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		if (sh == YSI_g_sTextSets[i][e_TEXT_SETS_HASH] && !strcmp(YSI_g_sReturnText, YSI_g_sTextSets[i][e_TEXT_SETS_NAME]))
		{
			// Got the start slot.
			return Text_FindEntry(YSI_g_sTextSets[i][e_TEXT_SETS_INDEX], bernstein(name));
		}
	}
	return -1;
}

/*----------------------------------------------------------------------------*\
Function:
	_Text_ReturnSingle
Params:
	str[] - String to be returned.
	len0 - The length of the string.
Return:
	-
Notes:
	This function, along with _Text_RemoteSingle, is used to get a single string
	from a remote script when the exact ID is known in advance and no other data
	like stlyes are required.
\*----------------------------------------------------------------------------*/

forward _Text_ReturnSingle(str[], len0);

public _Text_ReturnSingle(str[], len0) <y_text_get_text : n>
{
}

public _Text_ReturnSingle(str[], len0) <y_text_get_text : y>
{
	#pragma unused len0
	strcpy(YSI_g_sExtraText, str);
}

forward _Text_RemoteSingle(master, id);

public _Text_RemoteSingle(master, id)
{
	if (master == TEXT_MASTER)
	{
		CallRemoteFunction("_Text_ReturnSingle", "ai", YSI_g_sTextStrings[id], sizeof (YSI_g_sTextStrings[]));
	}
}

stock _Text_GetPointer(master, id)
{
	if (master == TEXT_MASTER)
	{
		//new
		//	ret = id * sizeof (YSI_g_sTextStrings[]);
		//printf("%d", ret);
		#emit CONST.alt   YSI_g_sTextStrings
		#emit LOAD.S.pri  id
		#emit IDXADDR
		#emit MOVE.alt
		#emit LOAD.i
		#emit ADD
		#emit RETN
	}
	state y_text_get_text : y;
	CallRemoteFunction("_Text_RemoteSingle", "ii", master, id);
	state y_text_get_text : n;
	#emit CONST.pri  YSI_g_sExtraText
	#emit RETN
	//#emit STOR.S.pri  ret
	//printf("%d", ret);
	// Never actually called, but shuts up the compiler
	return id;
}

/*----------------------------------------------------------------------------*\
Function:
	Text_GetStandard
Params:
	search[] - Search blocks to look in.
	nh - Hash of the string name to find.
	Language:l - Language in which to get the text.
Return:
	-
Notes:
	Gets a string in a language from a hash and a text set.
\*----------------------------------------------------------------------------*/

forward Text_GetStandard(search[], nh, Language:l);

public Text_GetStandard(search[], nh, Language:l)
{
	if (getproperty(8, YSIM_TXTFND) != -1)
	{
		return;
	}
	P:4("Text_GetStandard called");
	new
		sh = bernstein(search);//,
		//fail[MAX_INI_ENTRY_TEXT] = "String not found";
	for (new i = 0; i != Y_TEXT_MAX_SETS; ++i)
	{
		P:7("Text_GetStandard: %d, %d, %d", i, sh, YSI_g_sTextSets[i][e_TEXT_SETS_HASH]);
		if (sh == YSI_g_sTextSets[i][e_TEXT_SETS_HASH] && !strcmp(search, YSI_g_sTextSets[i][e_TEXT_SETS_NAME]))
		{
			// Got the start slot.
			new
				slot = Text_FindEntry(YSI_g_sTextSets[i][e_TEXT_SETS_INDEX], nh);
			P:7("Text_GetStandard: found %d, %d, %d", YSI_g_sTextSets[i][e_TEXT_SETS_INDEX], nh, slot);
			if (slot != -1)
			{
				new
					pointer = YSI_g_sTextEntries[slot][e_TEXT_ENTRY_POINTERS][l];
				P:5("Text_GetStandard: %d, %d, %d, %d", slot, pointer, getproperty(8, YSIM_TXTIND), _@);
				if (pointer != -1)
				{
					//P:5("Text_GetStandard: %08x%08x%08x%08x%08x", YSI_g_sTextStrings[pointer][0], YSI_g_sTextStrings[pointer][1], YSI_g_sTextStrings[pointer][2], YSI_g_sTextStrings[pointer][3], YSI_g_sTextStrings[pointer][4]);
					//setproperty(9, "", YSIM_STRING, YSI_g_sTextStrings[pointer]);
					setproperty(8, YSIM_TXTFND, _@);
					// I reckon this is the only time these functions have ever
					// been used together directly...
					P:5("Text_GetStandard: %s", YSI_g_sTextStrings[pointer]);
					//SetPVarString(getproperty(8, YSIM_TXTIND), "YSI_g_sString", YSI_g_sTextStrings[pointer]);
					//printf("%d %d %d %d %d = %d", YSI_g_sTextStrings[pointer][0], YSI_g_sTextStrings[pointer][1], YSI_g_sTextStrings[pointer][2], YSI_g_sTextStrings[pointer][3], YSI_g_sTextStrings[pointer][4], strlen(YSI_g_sTextStrings[pointer]));
					// TODO: Fix this for the new parameter list.
					new
						style[E_STYLE_DATA],
						label[E_3D_DATA];
					Styles_GetData(slot, style, label);
					CallRemoteFunction("Text_ReturnTheText", "iiaiaiai", TEXT_MASTER, pointer, YSI_g_sTextStrings[pointer], sizeof (YSI_g_sTextStrings[]), style, _:E_STYLE_DATA, label, _:E_3D_DATA);
					//format(fail, sizeof (fail), "%s", YSI_g_sTextStrings[pointer]);
					//printf("fail: %s", fail);
					//break;
					return;
				}
			}
			break;
		}
	}
	//static const
	//	fail[MAX_INI_ENTRY_TEXT] = "\1";
	setproperty(9, "", YSIM_STRING, "\1");
	//return fail;
}

/*----------------------------------------------------------------------------*\
Function:
	Text_GetText
Params:
	search[] - A "|" separated list of files and sections to search in.
	name[] - The text entry to look for.
	Language:l - The language to get.
Return:
	The specified string in the specified language.
Notes:
	Uses "YSI_g_sReturnText" instead of a normal return because this may call
	remote scripts which will need to return a standardised string as an array
	to preserve all the non-standard characters (passing data as a string using
	CallRemoteFunction packs the string, which we don't want).
\*----------------------------------------------------------------------------*/

// Get the loaded standardised version of this string.
//forward Text_GetStandard(start[] name[], Language:l);

static stock Text_GetText(search[], name[], Language:l)
{
	/*new
		start = Text_GetProvider(search),
		slot = Text_FindEntry(*/
	// TODO: Parse multiple search strings.
	//search[strlen(search) - 1] = '\0';
	//new loops = 0;
	//static
	//	ret[MAX_INI_ENTRY_TEXT];
	new
		pos,
		lst;
	state y_text_get_text:y;
	setproperty(8, YSIM_TXTFND, -1);
	//setproperty(8, YSIM_TXTIND, index);
	while (search[lst])
	{
		pos = strfind(search[lst], "|");
		P:7("Text_GetText: loop1 %s %d %d %s %d", search, lst, pos, search[lst], search[lst + pos + 1]);
		search[lst + pos] = '\0';
		CallRemoteFunction("Text_GetStandard", "sii", search[lst], bernstein(name), _:l);
		//P:7("Text_GetText: loop2 %s %d %d %s %d %d", search, lst, pos, search[lst], search[pos + 1], ret[0]);
		search[lst + pos] = '|';
		//if (ret[0] != '\1')
		if (getproperty(8, YSIM_TXTFND) != -1)
		{
			//getproperty(9, "", YSIM_STRING, ret);
			//strunpack(ret, ret)
			//GetPVarString(index, "YSI_g_sString", ret, MAX_INI_ENTRY_TEXT);
			state y_text_get_text:n;
			return;// ret;
		}
		lst += pos + 1;
		//if (++loops == 10) break;
		//break;
	}
	//if (ret[0] == '\1')
	{
		// Need better error reporting here.
		YSI_g_sReturnText = "Text Not Found";
	}
	state y_text_get_text:n;
	return;// ret;
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

// To be public.
//stock Text_GetProvider(search[])
//{
//}

static stock Text_FindEntry(start, hash)
{
	P:4("Text_FindEntry called: %i, %i", start, hash);
	while (start != -1)
	{
		new
			cmp = YSI_g_sTextEntries[start][e_TEXT_ENTRY_HASH] - hash;
		if (cmp < 0)
		{
			start = YSI_g_sTextEntries[start][e_TEXT_ENTRY_RIGHT];
		}
		else if (cmp > 0)
		{
			start = YSI_g_sTextEntries[start][e_TEXT_ENTRY_LEFT];
		}
		else
		{
			return start;
		}
	}
	return -1;
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

static stock Text_AddEntry(set, name[], value[], hash)
{
	P:4("Text_AddEntry called: %i, \"%s\", \"%s\", %i", set, name, value, hash);
	new
		entry = Text_GetFreeEntry(),
		slot = Text_GetFreeSlot();
	if (entry == -1 || slot == -1)
	{
		P:E("Text buffer full!");
		return;
	}
	// Save the string.
	//strpack(YSI_g_sTextStrings[slot], value, MAX_INI_ENTRY_TEXT * 4);
	Format_Standardise(value, YSI_g_sTextStrings[slot], MAX_INI_ENTRY_TEXT);
	//strpack(YSI_g_sTextStrings[slot], value, MAX_INI_ENTRY_TEXT * 4);
	//strcpy(YSI_g_sTextStrings[slot], value, MAX_INI_ENTRY_TEXT * 4);
	// Save the identifier.
	P:5("Text_AddEntry: slot = %d %d,%d,%d,%d,%d", slot, YSI_g_sTextStrings[slot][0], YSI_g_sTextStrings[slot][1], YSI_g_sTextStrings[slot][2], YSI_g_sTextStrings[slot][3], YSI_g_sTextStrings[slot][4]);
	strpack(YSI_g_sTextEntries[entry][e_TEXT_ENTRY_NAME], name, MAX_INI_ENTRY_NAME * 4);
	//strcpy(YSI_g_sTextEntries[entry][e_TEXT_ENTRY_NAME], name, MAX_INI_ENTRY_NAME * 4);
	YSI_g_sTextEntries[entry][e_TEXT_ENTRY_HASH] = hash;
	YSI_g_sTextEntries[entry][e_TEXT_ENTRY_POINTERS][YSI_g_sLangIndex] = slot;
	// Insert the identifier.
	new
		idx = YSI_g_sTextSets[set][e_TEXT_SETS_INDEX],
		next;
	if (idx == -1)
	{
		YSI_g_sTextSets[set][e_TEXT_SETS_INDEX] = entry;
	}
	else
	{
		for ( ; ; )
		{
			new
				cmp = YSI_g_sTextEntries[idx][e_TEXT_ENTRY_HASH] - hash;
			if (cmp < 0)
			{
				next = YSI_g_sTextEntries[idx][e_TEXT_ENTRY_RIGHT];
				if (next == -1)
				{
					YSI_g_sTextEntries[idx][e_TEXT_ENTRY_RIGHT] = entry;
					return;
				}
			}
			else if (cmp > 0)
			{
				next = YSI_g_sTextEntries[idx][e_TEXT_ENTRY_LEFT];
				if (next == -1)
				{
					YSI_g_sTextEntries[idx][e_TEXT_ENTRY_LEFT] = entry;
					return;
				}
			}
			idx = next;
		}
	}
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

static stock Text_GetFreeSlot()
{
	P:4("Text_GetFreeSlot called");
	if (YSI_g_sUnusedSlot == MAX_TEXT_ENTRIES * _:YSI_MAX_LANGUAGES)
	{
		return -1;
	}
	new
		ret = YSI_g_sUnusedSlot;
	YSI_g_sUnusedSlot = YSI_g_sTextStrings[ret][0];
	YSI_g_sTextStrings[ret][0] = '\0';
	return ret;
}

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

static stock Text_GetFreeEntry()
{
	P:4("Text_GetFreeEntry called");
	if (YSI_g_sUnusedEntry == MAX_TEXT_ENTRIES)
	{
		return -1;
	}
	new
		ret = YSI_g_sUnusedEntry;
	YSI_g_sUnusedEntry = YSI_g_sTextEntries[ret][e_TEXT_ENTRY_HASH];
	YSI_g_sTextEntries[ret][e_TEXT_ENTRY_HASH] = 0;
	YSI_g_sTextEntries[ret][e_TEXT_ENTRY_LEFT] = -1;
	YSI_g_sTextEntries[ret][e_TEXT_ENTRY_RIGHT] = -1;
	// Reset text storage pointers.
	for (new Language:i = Language:0; i != YSI_MAX_LANGUAGES; ++i)
	{
		YSI_g_sTextEntries[ret][e_TEXT_ENTRY_POINTERS][i] = -1;
	}
	return ret;
}

static stock
	YSI_g_sLangBuffer[YSI_MAX_LANGUAGES][1024];

/*----------------------------------------------------------------------------*\
Function:
	@PlayerSet:players - A representation of players to show to.
	search[] - Text sets to look in for this string.
	ident[] - The name of the string to look for (or the string itself).
	GLOBAL_TAG_TYPES:... - All the parameters to pass to the string.
Params:
	-
Return:
	-
Notes:
	TODO: Change the code to push the parameters to Format_Render only once and
	reuse the resulting stack.
\*----------------------------------------------------------------------------*/

stock _Text_Send(@PlayerSet:players, search[], ident[], GLOBAL_TAG_TYPES:...)
{
	setproperty(8, YSIM_TXTLEN, 1023);
	new
		bool:wasOnce[YSI_MAX_LANGUAGES] = {false, ...},
		n = (numargs() - 3) * 4;
	// Loop through all the players passed to the function (however they were
	// passed to the function).  This uses "@PlayerArray" instead of
	// "@PlayerVar" so that we can optimised for multiple languages.  Now it
	// uses "@PlayerSet" instead because that's the new one designed for
	// situations like this in which we want direct access to the variable.
	if (n)
	{
		new
			arg_start,
			arg_end;
		// Load the real address of the last static parameter.  Do this by
		// loading the address of the parameter and then adding the value of
		// [FRM] (frame pointer).
		#emit CONST.alt                 ident
		#emit LCTRL                     5
		#emit ADD
		#emit STOR.S.pri                arg_start
		// Load the address of the last variable parameter.  Do this by adding
		// the number of variables on the value just loaded.
		#emit LOAD.S.alt                n
		#emit ADD
		#emit STOR.S.pri                arg_end
		// Push the variable arguments.  This is done by loading the value of
		// each one in reverse order and pushing them.  I'd love to be able to
		// rewrite this to use the values of pri and alt for comparison, instead
		// of having or constantly reload two variables.
		foreach (new playerid : PS(players))
		//new playerid = 0;
		{
			new
				Language:lang = Langs_GetPlayerLanguage(playerid);
			if (lang == NO_LANGUAGE)
			{
				lang = Language:0;
			}
			if (wasOnce[lang])
			{
				// Optimisation for sending messages to multiple players.
				Format_JustShow(playerid, YSI_g_sLangBuffer[lang]);
				continue;
			}
			new
				arg_cur = arg_end,
				ret;
			do
			{
				#emit LOAD.S.pri            arg_cur
				#emit LOAD.I
				#emit PUSH.pri
				arg_cur -= 4;
			}
			while (arg_cur > arg_start);
			switch (search[0])
			{
				case '\1':
				{
					// Null - error.
					P:E("Text_Send called with NULL");
				}
				case '\2':
				{
					// None - just print the string directly.
					ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, ident, n / 4);
				}
				case '\3':
				{
					new
						pos = strfind(ident, ">"),
						br = strfind(ident, "[");
					if (pos > 1 && br != -1)
					{
						ident[pos - 2] = '\0';
						if (br)
						{
							ident[br] = ':';
							Text_GetText(ident, ident[pos + 1], lang);
						}
						else
						{
							Text_GetText(ident[1], ident[pos + 1], lang);
						}
						ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, YSI_g_sReturnText, n / 4);
					}
					else
					{
						// Var - May contain location search information in
						// "name".  Maybe add an "all" search parameter...
						ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, "Could not find text source", n / 4);
						// The code generated messes up the pushing.
					}
				}
				default:
				{
					Text_GetText(search, ident, lang);
					ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, YSI_g_sReturnText, n / 4);
				}
			}
			#emit LCTRL                     4
			#emit LOAD.S.alt                n
			#emit ADD
			#emit SCTRL                     4
			wasOnce[lang] = !ret;
		}
	}
	else
	{
		foreach (new playerid : PS(players))
		{
			new
				Language:lang = Langs_GetPlayerLanguage(playerid);
			if (wasOnce[lang])
			{
				// Optimisation for sending messages to multiple players.
				Format_JustShow(playerid, YSI_g_sLangBuffer[lang]);
				continue;
			}
			switch (search[0])
			{
				case '\1':
				{
					// Null - error.
					P:E("Text_Send called with NULL");
				}
				case '\2':
				{
					// None - just print the string directly.
					wasOnce[lang] = !Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, ident, 0);
				}
				case '\3':
				{
					new
						pos = strfind(ident, ">"),
						br = strfind(ident, "[");
					if (pos > 1 && br != -1)
					{
						ident[pos - 2] = '\0';
						if (br)
						{
							ident[br] = ':';
							Text_GetText(ident, ident[pos + 1], lang);
						}
						else
						{
							Text_GetText(ident[1], ident[pos + 1], lang);
						}
						wasOnce[lang] = !Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, YSI_g_sReturnText, 0);
					}
					else
					{
						// Var - May contain location search information in
						// "name".  Maybe add an "all" search parameter...
						wasOnce[lang] = !Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, "Could not find text source", 0);
					}
				}
				default:
				{
					Text_GetText(search, ident, lang);
					wasOnce[lang] = !Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], getproperty(8, YSIM_TXTLEN) - 1, 0, 0, YSI_g_sReturnText, 0);
				}
			}
		}
	}
	return Text_GetLastID();
}

static stock
	YSI_g_sCaptionText[YSI_MAX_LANGUAGES][64],
	YSI_g_sButton1Text[YSI_MAX_LANGUAGES][16],
	YSI_g_sButton2Text[YSI_MAX_LANGUAGES][16];

/*----------------------------------------------------------------------------*\
Function:
	-
Params:
	-
Return:
	-
Notes:
	-
\*----------------------------------------------------------------------------*/

//stock @PlayerArray:players<MAX_PLAYERS>, callback[], csearch[], cident[], isearch[], iident[], b1search[], b1ident[], b2search[], b2ident[], GLOBAL_TAG_TYPES:...)
//stock _Text_MessageBox(@PlayerArray:players<MAX_PLAYERS>, callback[], csearch[], cident[], isearch[], iident[], b1search[], b1ident[], b2search[], b2ident[], GLOBAL_TAG_TYPES:...)

//stock _Text_MessageBox(@PlayerArray:players<MAX_PLAYERS>, callback[], csearch[], cident[], isearch[], iident[], b1search[], b1ident[], b2search[], b2ident[], GLOBAL_TAG_TYPES:...)
stock _Text_DialogBox(@PlayerSet:players, style, callback:callback, csearch[], cident[], isearch[], iident[], b1search[], b1ident[], b2search[], b2ident[], GLOBAL_TAG_TYPES:...)
//stock _Text_MessageBox(__ps_addr_t:__ps_addr, callback[], csearch[], cident[], isearch[], iident[], b1search[], b1ident[], b2search[], b2ident[], GLOBAL_TAG_TYPES:...)
{
	// This is what the "@PlayerArray" macro expands to, but I needed more
	// control to allow me to return a value.  The fact that I can't ATM is a
	// serious problem, but not serious enough to warrant fixing.
	new
		bool:wasOnce[YSI_MAX_LANGUAGES] = {false, ...},
		bool:gotExtras[YSI_MAX_LANGUAGES] = {false, ...},
		n = (numargs() - 10) * 4,
		dialog = Dialog_ObtainID();
	// Loop through all the players passed to the function (however they were
	// passed to the function).  This uses "@PlayerArray" instead of
	// "@PlayerVar" so that we can optimised for multiple languages.
	if (n)
	{
		new
			arg_start,
			arg_end;
		// Load the real address of the last static parameter.  Do this by
		// loading the address of the parameter and then adding the value of
		// [FRM] (frame pointer).
		#emit CONST.alt                 b2ident
		#emit LCTRL                     5
		#emit ADD
		#emit STOR.S.pri                arg_start
		// Load the address of the last variable parameter.  Do this by adding
		// the number of variables on the value just loaded.
		#emit LOAD.S.alt                n
		#emit ADD
		#emit STOR.S.pri                arg_end
		// Push the variable arguments.  This is done by loading the value of
		// each one in reverse order and pushing them.  I'd love to be able to
		// rewrite this to use the values of pri and alt for comparison, instead
		// of having or constantly reload two variables.
		//for (new PlayerArray:players<MAX_PLAYERS>; __PS_A(_:__ps_addr, players); )
		foreach (new playerid : PS(players))
		{
			new
				Language:lang = Langs_GetPlayerLanguage(playerid);
			if (lang == NO_LANGUAGE)
			{
				lang = Language:0;
			}
			// Only ever do this part once per language.
			if (!gotExtras[lang])
			{
				// Get the text for the caption in this language.
				switch (csearch[0])
				{
					case '\1': P:E("Text_Send called with NULL");
					case '\2': strcpy(YSI_g_sCaptionText[lang], cident, sizeof (YSI_g_sCaptionText[]));
					case '\3':
					{
						new pos = strfind(cident, ">"), br = strfind(cident, "[");
						if (pos > 1 && br != -1)
						{
							cident[pos - 2] = '\0';
							if (br) cident[br] = ':', Text_GetText(cident, cident[pos + 1], lang);
							else Text_GetText(cident[1], cident[pos + 1], lang);
							strcpy(YSI_g_sCaptionText[lang], YSI_g_sReturnText, sizeof (YSI_g_sCaptionText[]));
						}
						else YSI_g_sCaptionText[lang] = "Could not find title source";
					}
					default: Text_GetText(csearch, cident, lang), strcpy(YSI_g_sCaptionText[lang], YSI_g_sReturnText, sizeof (YSI_g_sCaptionText[]));
				}
				// Get the text for button 1 in this language.
				switch (b1search[0])
				{
					case '\1': P:E("Text_Send called with NULL");
					case '\2': strcpy(YSI_g_sButton1Text[lang], b1ident, sizeof (YSI_g_sButton1Text[]));
					case '\3':
					{
						new pos = strfind(b1ident, ">"), br = strfind(b1ident, "[");
						if (pos > 1 && br != -1)
						{
							b1ident[pos - 2] = '\0';
							if (br) b1ident[br] = ':', Text_GetText(b1ident, b1ident[pos + 1], lang);
							else Text_GetText(b1ident[1], b1ident[pos + 1], lang);
							strcpy(YSI_g_sButton1Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton1Text[]));
						}
						else YSI_g_sButton1Text[lang] = "Not Found";
					}
					default: Text_GetText(b1search, b1ident, lang), strcpy(YSI_g_sButton1Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton1Text[]));
				}
				// Get the text for button 1 in this language.
				if (b2ident[0])
				{
					switch (b2search[0])
					{
						case '\1': P:E("Text_Send called with NULL");
						case '\2': strcpy(YSI_g_sButton2Text[lang], b2ident, sizeof (YSI_g_sButton2Text[]));
						case '\3':
						{
							new pos = strfind(b2ident, ">"), br = strfind(b2ident, "[");
							if (pos > 1 && br != -1)
							{
								b2ident[pos - 2] = '\0';
								if (br) b2ident[br] = ':', Text_GetText(b2ident, b2ident[pos + 1], lang);
								else Text_GetText(b2ident[1], b2ident[pos + 1], lang);
								strcpy(YSI_g_sButton2Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton2Text[]));
							}
							else YSI_g_sButton2Text[lang] = "";
						}
						default: Text_GetText(b2search, b2ident, lang), strcpy(YSI_g_sButton2Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton2Text[]));
					}
				}
				else YSI_g_sButton2Text[lang] = "";
				gotExtras[lang] = true;
			}
			if (!wasOnce[lang])
			{
				// Optimisation for sending messages to multiple players.
				//Format_DoDisplay(playerid, 0xFF0000AA, YSI_g_sLangBuffer[lang]);
				//continue;
				new
					arg_cur = arg_end,
					ret;
				do
				{
					#emit LOAD.S.pri            arg_cur
					#emit LOAD.I
					#emit PUSH.pri
					arg_cur -= 4;
				}
				while (arg_cur > arg_start);
				switch (isearch[0])
				{
					case '\1':
					{
						// Null - error.
						P:E("Text_Send called with NULL");
					}
					case '\2':
					{
						// None - just print the string directly.
						ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, iident, n / 4);
					}
					case '\3':
					{
						new
							pos = strfind(iident, ">"),
							br = strfind(iident, "[");
						if (pos > 1 && br != -1)
						{
							iident[pos - 2] = '\0';
							if (br)
							{
								iident[br] = ':';
								Text_GetText(iident, iident[pos + 1], lang);
							}
							else
							{
								Text_GetText(iident[1], iident[pos + 1], lang);
							}
							ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, YSI_g_sReturnText, n / 4);
						}
						else
						{
							// Var - May contain location isearch information in
							// "name".  Maybe add an "all" isearch parameter...
							ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, "Could not find text source", n / 4);
							// The code generated messes up the pushing.
						}
					}
					default:
					{
						Text_GetText(isearch, iident, lang);
						ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, YSI_g_sReturnText, n / 4);
					}
				}
				#emit LCTRL                     4
				#emit LOAD.S.alt                n
				#emit ADD
				#emit SCTRL                     4
				wasOnce[lang] = !ret;
			}
			// Display the message.
			Dialog_Show(playerid, style, YSI_g_sCaptionText[lang], YSI_g_sLangBuffer[lang], YSI_g_sButton1Text[lang], YSI_g_sButton2Text[lang], dialog);
		}
	}
	else
	{
		// Push the variable arguments.  This is done by loading the value of
		// each one in reverse order and pushing them.  I'd love to be able to
		// rewrite this to use the values of pri and alt for comparison, instead
		// of having or constantly reload two variables.
		foreach (new playerid : PS(players))
		//for (new PlayerArray:players<MAX_PLAYERS>; __PS_A(_:__ps_addr, players); )
		{
			new
				Language:lang = Langs_GetPlayerLanguage(playerid);
			if (lang == NO_LANGUAGE)
			{
				lang = Language:0;
			}
			// Only ever do this part once per language.
			if (!gotExtras[lang])
			{
				// Get the text for the caption in this language.
				switch (csearch[0])
				{
					case '\1': P:E("Text_Send called with NULL");
					case '\2': strcpy(YSI_g_sCaptionText[lang], cident, sizeof (YSI_g_sCaptionText[]));
					case '\3':
					{
						new pos = strfind(cident, ">"), br = strfind(cident, "[");
						if (pos > 1 && br != -1)
						{
							cident[pos - 2] = '\0';
							if (br) cident[br] = ':', Text_GetText(cident, cident[pos + 1], lang);
							else Text_GetText(cident[1], cident[pos + 1], lang);
							strcpy(YSI_g_sCaptionText[lang], YSI_g_sReturnText, sizeof (YSI_g_sCaptionText[]));
						}
						else YSI_g_sCaptionText[lang] = "Could not find title source";
					}
					default: Text_GetText(csearch, cident, lang), strcpy(YSI_g_sCaptionText[lang], YSI_g_sReturnText, sizeof (YSI_g_sCaptionText[]));
				}
				// Get the text for button 1 in this language.
				switch (b1search[0])
				{
					case '\1': P:E("Text_Send called with NULL");
					case '\2': strcpy(YSI_g_sButton1Text[lang], b1ident, sizeof (YSI_g_sButton1Text[]));
					case '\3':
					{
						new pos = strfind(b1ident, ">"), br = strfind(b1ident, "[");
						if (pos > 1 && br != -1)
						{
							b1ident[pos - 2] = '\0';
							if (br) b1ident[br] = ':', Text_GetText(b1ident, b1ident[pos + 1], lang);
							else Text_GetText(b1ident[1], b1ident[pos + 1], lang);
							strcpy(YSI_g_sButton1Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton1Text[]));
						}
						else YSI_g_sButton1Text[lang] = "Not Found";
					}
					default: Text_GetText(b1search, b1ident, lang), strcpy(YSI_g_sButton1Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton1Text[]));
				}
				// Get the text for button 1 in this language.
				if (b2ident[0])
				{
					switch (b2search[0])
					{
						case '\1': P:E("Text_Send called with NULL");
						case '\2': strcpy(YSI_g_sButton2Text[lang], b2ident, sizeof (YSI_g_sButton2Text[]));
						case '\3':
						{
							new pos = strfind(b2ident, ">"), br = strfind(b2ident, "[");
							if (pos > 1 && br != -1)
							{
								b2ident[pos - 2] = '\0';
								if (br) b2ident[br] = ':', Text_GetText(b2ident, b2ident[pos + 1], lang);
								else Text_GetText(b2ident[1], b2ident[pos + 1], lang);
								strcpy(YSI_g_sButton2Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton2Text[]));
							}
							else YSI_g_sButton2Text[lang] = "";
						}
						default: Text_GetText(b2search, b2ident, lang), strcpy(YSI_g_sButton2Text[lang], YSI_g_sReturnText, sizeof (YSI_g_sButton2Text[]));
					}
				}
				else YSI_g_sButton2Text[lang] = "";
				gotExtras[lang] = true;
			}
			if (!wasOnce[lang])
			{
				new
					ret;
				switch (isearch[0])
				{
					case '\1':
					{
						// Null - error.
						P:E("Text_Send called with NULL");
					}
					case '\2':
					{
						// None - just print the string directly.
						ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, iident, n / 4);
					}
					case '\3':
					{
						new
							pos = strfind(iident, ">"),
							br = strfind(iident, "[");
						if (pos > 1 && br != -1)
						{
							iident[pos - 2] = '\0';
							if (br)
							{
								iident[br] = ':';
								Text_GetText(iident, iident[pos + 1], lang);
							}
							else
							{
								Text_GetText(iident[1], iident[pos + 1], lang);
							}
							ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, YSI_g_sReturnText, n / 4);
						}
						else
						{
							// Var - May contain location isearch information in
							// "name".  Maybe add an "all" isearch parameter...
							ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, "Could not find text source", n / 4);
							// The code generated messes up the pushing.
						}
					}
					default:
					{
						Text_GetText(isearch, iident, lang);
						ret = Format_Render(playerid, lang, YSI_g_sLangBuffer[lang], sizeof (YSI_g_sLangBuffer[]) - 1, 0, e_FORMAT_FLAGS_NONE, YSI_g_sReturnText, n / 4);
					}
				}
				wasOnce[lang] = !ret;
			}
			// Display the message.
			Dialog_Show(playerid, style, YSI_g_sCaptionText[lang], YSI_g_sLangBuffer[lang], YSI_g_sButton1Text[lang], YSI_g_sButton2Text[lang], dialog);
		}
	}
	// Sort out the callbacks etc for the dialog return.
	new
		cd[E_CALLBACK_DATA];
	Callback_Get(callback, cd, _F<iiiis>);
	Dialog_SetCallbackData(dialog, cd); //AMX_GetPublicPointer(callback));
	return dialog;
}
