/*----------------------------------------------------------------------------*-
					=================================
					Y Sever Includes - Text Draw Core
					=================================
Description:
	Provides a wrapper to the SA:MP text_draw functions offering much
	improved functionality including XML definable styles, safe display,
	timed displays, styles and text separated, styles updateable dynamically
	and more.
Legal:
	Copyright (C) 2007 Alex "Y_Less" Cole

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
	MA 02110-1301, USA.
Version:
	0.1
Changelog:
	18/02/08:
		Improved garbage collection.
		Added cloning.
		Added naming created styles.
	17/02/08:
		Added optional garbage collection.
	28/12/07:
		First version.
Functions:
	Public:
		TD_LoadColour - Callback for XML file loading.
		TD_Textdraw - Callback for XML file loading.
		TD_HideForPlayer - Hides text from a player.
	Core:
		TD_TD - Sets up the system for use.
		TD_OnPlayerDisconnect - Resets texts and variables.
	Stock:
		TD_Clone - Creates a copy of a style with a new name.
		TD_Parse - Parses an XML file of textdraw data.
		TD_Name - Names a given text draw style.
		TD_GetID - Gets the ID of a hashed style name.
		TD_Create - Creates a new style.
		TD_TextPosition - Sets the position of an instance.
		TD_TextXPos - Sets the x position of an instance.
		TD_TextYPos - Sets the y position of an instance.
		TD_StylePosition - Sets the position of a style.
		TD_StyleXPos - Sets the x position of a style.
		TD_StyleYPos - Sets the y position of a style.
		TD_LetterSize - Sets a style's letter size.
		TD_LetterX - Sets a style's letter x size.
		TD_LetterY - Sets a style's letter y size.
		TD_TextSize - Sets a style's text size.
		TD_TextX - Sets a style's text x size.
		TD_TextY - Sets a style's text y size.
		TD_Alignment - Sets a style's alignment.
		TD_Colour - Sets a style's colour.
		TD_UseBox - Sets wether a style uses a box.
		TD_BoxColour - Sets a style's box colour.
		TD_SetShadow - Sets a style's shadow.
		TD_SetOutline - Sets a style's outline.
		TD_BackgroundColour - Sets a style's background colour.
		TD_Font - Sets a style's font.
		TD_SetProportional - Sets wether a style is proportional.
		TD_SetTime - Sets a style's display time.
		TD_Update - Updates all instances of a style with new data.
		TD_UpdateOne - Updates a single instance.
		TD_Delete - Deletes a style and all instances.
		TD_Destroy - Destroys a single instance.
		TD_Display - Creates an instance of a style with a given message.
		TD_DisplayHashed - Creates an instance from a hashed style name.
		TD_DisplayNamed - Creates an instance of a named style.
		TD_ShowForPlayer - Shows an instance to a player.
		TD_HideForPlayer - Hides an instance from a player.
		TD_ShowForAll - Shows an instance to everyone.
		TD_HideForAll - Hides an instance from everyone.
		TD_Garbage - Marks a real textdraw as removable when unused.
	Static:
		TD_Render - Generates a SA:MP textdraw.
	Inline:
		TD_IsValid - Checks if a given style is valid.
	API:
		-
Callbacks:
	-
Definitions:
	MAX_TEXT_DRAW_STYLES - Number of storable styles.
	TEXT_DRAW_NO_NEXT - List end id.
	MAX_TEXT_DRAW_LINE - Length of text in an instance.
Enums:
	td_align - Where the textdraw will go.
	e_TD_BITS - Style flags.
	E_TD_DATA - Data to store on a style.
	E_TD_DISPLAY - Data to store on an instance.
Macros:
	-
Tags:
	Bit - Bit array type.
Variables:
	Global:
		-
	Static:
		YSI_g_sXMLRules - XML parsing options handle.
		YSI_g_sTDData - Stored styles.
		YSI_g_sTDDisplay - Stored instances.
		YSI_g_sUnused - Unused instances.
		YSI_g_sPlayerDraws - Text draws a player can see.
Commands:
	-
Compile options:
	-
-*----------------------------------------------------------------------------*/

#if !defined MAX_TEXT_DRAW_STYLES
	#define MAX_TEXT_DRAW_STYLES (Style:32)
#endif
#define MAX_TEXT_DRAW_LINE (128)
#define TEXT_DRAW_NO_NEXT (Text:-1)

enum td_align
{
	td_align_none,
	td_align_left,
	td_align_center,
	td_align_centre = td_align_center,
	td_align_right
}

enum e_TD_BITS (<<= 1)
{
	e_TD_BITS_SHADOW = 0x000000FF,
	e_TD_BITS_OUTLINE = 0x0000FF00,
	e_TD_BITS_ALIGN = 0x000F0000,
	e_TD_BITS_FONT = 0x00F00000,
	e_TD_BITS_BOX = 0x01000000,
	e_TD_BITS_PROP
}

enum E_TD_DATA
{
	E_TD_DATA_NAME[MAX_XML_ENTRY_NAME],
	E_TD_DATA_HASH,
	Float:E_TD_DATA_X,
	Float:E_TD_DATA_Y,
	Float:E_TD_DATA_LX,
	Float:E_TD_DATA_LY,
	Float:E_TD_DATA_TX,
	Float:E_TD_DATA_TY,
	E_TD_DATA_COLOUR,
	e_TD_BITS:E_TD_DATA_BITS,
	E_TD_DATA_BOX,
	E_TD_DATA_BG,
	E_TD_DATA_TIME,
	Text:E_TD_DATA_USE
}

enum E_TD_DISPLAY
{
	Float:E_TD_DISPLAY_X,
	Float:E_TD_DISPLAY_Y,
	Text:E_TD_DISPLAY_NEXT,
	Style:E_TD_DISPLAY_STYLE,
	E_TD_DISPLAY_LIFE,
	Text:E_TD_DISPLAY_REAL,
	E_TD_DISPLAY_TEXT[MAX_TEXT_DRAW_LINE]
}

forward TD_LoadColour();
forward TD_Textdraw();
forward TD_HideForPlayer(playerid, Text:textDraw);

static
	XML:YSI_g_sXMLRules = NO_XML_FILE,
	YSI_g_sTDData[MAX_TEXT_DRAW_STYLES][E_TD_DATA],
	YSI_g_sTDDisplay[Text:MAX_TEXT_DRAWS][E_TD_DISPLAY],
	YSI_g_sTDTimers[Text:MAX_TEXT_DRAWS][MAX_PLAYERS],
	Text:YSI_g_sUnused,
	Bit:YSI_g_sPlayerDraws[MAX_PLAYERS][Bit_Bits(MAX_TEXT_DRAWS)];

/*----------------------------------------------------------------------------*-
Function:
	TD_IsValid
Params:
	Style:id - Text draw style to check validity of,
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define TD_IsValid(%1) \
	((%1) >= Style:0 && (%1) < MAX_TEXT_DRAW_STYLES && YSI_g_sTDData[(%1)][E_TD_DATA_NAME][0])

/*----------------------------------------------------------------------------*-
Function:
	TD_TD
Params:
	-
Return:
	-
Notes:
	Constructor.
-*----------------------------------------------------------------------------*/

TD_TD()
{
	if (YSI_g_sXMLRules == NO_XML_FILE)
	{
		YSI_g_sXMLRules = XML_New();
		if (YSI_g_sXMLRules != NO_XML_FILE)
		{
			XML_AddHandler(YSI_g_sXMLRules, "color", "TD_LoadColour");
			XML_AddHandler(YSI_g_sXMLRules, "colour", "TD_LoadColour");
			XML_AddHandler(YSI_g_sXMLRules, "textdraw", "TD_Textdraw");
			XML_AddHandler(YSI_g_sXMLRules, "box", "TD_Box");
			XML_AddHandler(YSI_g_sXMLRules, "background", "TD_Background");
			XML_AddHandler(YSI_g_sXMLRules, "style", "TD_Style");
		}
	}
	for (new Text:i; Text:i < Text:MAX_TEXT_DRAWS; i++)
	{
		YSI_g_sTDDisplay[i][E_TD_DISPLAY_REAL] = Text:INVALID_TEXT_DRAW;
		YSI_g_sTDDisplay[i][E_TD_DISPLAY_NEXT] = i + Text:1;
	}
	YSI_g_sTDDisplay[Text:(MAX_TEXT_DRAWS - 1)][E_TD_DISPLAY_NEXT] = TEXT_DRAW_NO_NEXT;
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Parse
Params:
	filename[] - File to parse as a textdraw data file.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_Parse(filename[])
{
	return XML_Parse(YSI_g_sXMLRules, filename);
}

/*----------------------------------------------------------------------------*-
Function:
	TD_LoadColour
Params:
	-
Return:
	-
Notes:
	XML callback for loading the <colour> tag.
-*----------------------------------------------------------------------------*/

public TD_LoadColour()
{
	static
		name[MAX_XML_ENTRY_NAME],
		val[MAX_XML_ENTRY_TEXT];
	new
		colour,
		hash;
	while (XML_GetKeyValue(name, val))
	{
		if (!strcmp(name, "name", true))
		{
			hash = bernstein(val);
		}
		else if (!strcmp(name, "hex", true))
		{
			colour = hexstr(val);
		}
	}
	if (hash) Text_SetColour(hash, colour);
	return colour;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Textdraw
Params:
	-
Return:
	-
Notes:
	XML callback for loading the <textdraw> tag.
-*----------------------------------------------------------------------------*/

public TD_Textdraw()
{
	new
		Style:i;
	while (i < MAX_TEXT_DRAW_STYLES && YSI_g_sTDData[i][E_TD_DATA_NAME][0])
	{
		i++;
	}
	if (i != MAX_TEXT_DRAW_STYLES)
	{
		static
			name[MAX_XML_ENTRY_NAME],
			val[MAX_XML_ENTRY_TEXT];
		new
			e_TD_BITS:bits;
		while (XML_GetKeyValue(name, val))
		{
			if (!strcmp(name, "x", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_X] = floatstr(val);
			}
			else if (!strcmp(name, "y", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_Y] = floatstr(val);
			}
			else if (!strcmp(name, "letterx", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_LX] = floatstr(val);
			}
			else if (!strcmp(name, "lettery", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_LY] = floatstr(val);
			}
			else if (!strcmp(name, "textx", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_TX] = floatstr(val);
			}
			else if (!strcmp(name, "texty", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_TY] = floatstr(val);
			}
			else if (!strcmp(name, "alignment", true))
			{
				bits &= ~e_TD_BITS_ALIGN;
				if (!strcmp(val, "left")) bits |= e_TD_BITS:(_:td_align_left << 16) & e_TD_BITS_ALIGN;
				else if (!strcmp(val, "right")) bits |= e_TD_BITS:(_:td_align_right << 16) & e_TD_BITS_ALIGN;
				else if (!strcmp(val, "center")) bits |= e_TD_BITS:(_:td_align_center << 16) & e_TD_BITS_ALIGN;
			}
			else if (!strcmp(name, "color", true) || !strcmp(name, "colour", true))
			{
				if (ishex(val)) YSI_g_sTDData[i][E_TD_DATA_COLOUR] = hexstr(val);
				else if (isnumeric(val)) YSI_g_sTDData[i][E_TD_DATA_COLOUR] = strval(val);
				else YSI_g_sTDData[i][E_TD_DATA_COLOUR] = Text_GetColour(bernstein(val));
			}
			else if (!strcmp(name, "box", true))
			{
				new
					box = strval(val);
				if (box)
				{
					bits |= e_TD_BITS_BOX;
					YSI_g_sTDData[i][E_TD_DATA_BOX] = box;
				}
			}
			else if (!strcmp(name, "shadow", true))
			{
				bits = (bits & ~e_TD_BITS_SHADOW) | (e_TD_BITS:(strval(val)) & e_TD_BITS_SHADOW);
			}
			else if (!strcmp(name, "outline", true))
			{
				bits = (bits & ~e_TD_BITS_OUTLINE) | (e_TD_BITS:(strval(val) << 8) & e_TD_BITS_OUTLINE);
			}
			else if (!strcmp(name, "background", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_BG] = hexstr(val);
			}
			else if (!strcmp(name, "font", true))
			{
				bits = (bits & ~e_TD_BITS_FONT) | (e_TD_BITS:(strval(val) << 20) & e_TD_BITS_FONT);
			}
			else if (!strcmp(name, "proportional", true))
			{
				bits |= e_TD_BITS_PROP;
			}
			else if (!strcmp(name, "time", true))
			{
				YSI_g_sTDData[i][E_TD_DATA_TIME] = strval(val);
			}
			else if (!strcmp(name, "name", true))
			{
				strcpy(YSI_g_sTDData[i][E_TD_DATA_NAME], val, MAX_XML_ENTRY_NAME);
			}
		}
		YSI_g_sTDData[i][E_TD_DATA_BITS] = bits;
		if (!YSI_g_sTDData[i][E_TD_DATA_NAME][0])
		{
			YSI_g_sTDData[i][E_TD_DATA_NAME][0] = 1;
			YSI_g_sTDData[i][E_TD_DATA_NAME][1] = 0;
		}
		YSI_g_sTDData[i][E_TD_DATA_HASH] = bernstein(YSI_g_sTDData[i][E_TD_DATA_NAME]);
		YSI_g_sTDData[i][E_TD_DATA_USE] = TEXT_DRAW_NO_NEXT;
		DBGP5("TD data: %.2f %.2f %.2f %.2f %.2f %.2f %d %d %d %d %d %s", YSI_g_sTDData[i][E_TD_DATA_X], YSI_g_sTDData[i][E_TD_DATA_Y], YSI_g_sTDData[i][E_TD_DATA_LX], YSI_g_sTDData[i][E_TD_DATA_LY], YSI_g_sTDData[i][E_TD_DATA_TX], YSI_g_sTDData[i][E_TD_DATA_TY], YSI_g_sTDData[i][E_TD_DATA_COLOUR], YSI_g_sTDData[i][E_TD_DATA_BOX], YSI_g_sTDData[i][E_TD_DATA_BG], YSI_g_sTDData[i][E_TD_DATA_TIME], YSI_g_sTDData[i][E_TD_DATA_BITS], YSI_g_sTDData[i][E_TD_DATA_NAME]);
	}
	return _:i;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_GetID
Params:
	hash - Hash of a style name to get a style index for.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock Style:TD_GetID(hash)
{
	new
		Style:i;
	while (i < MAX_TEXT_DRAW_STYLES && YSI_g_sTDData[i][E_TD_DATA_HASH] != hash)
	{
		i++;
	}
	return i;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Create
Params:
	Float:x - X position the text will appear at.
	Float:y - Y position the text will appear at,
	Float:letterX - Letter X size.
	Float:letterY - Letter Y size.
	Float:textX - Box X size.
	Float:textY - Box Y size.
	colour - Text colour.
	boxColour - Colour of the text box.
	bgColour - Colour of the text background.
	shadow - Text shadow size.
	outline - Text outline size.
	align - Text alignment.
	font - Text font style.
	bool:proportional - Wether to make the text proportional.
	bool:box - Wether to use a box.
	time - Time for the text to display in ms (0 = infinate).
	name[] - Name of the style.
Return:
	-
Notes:
	Creates a text draw style structure according to given
	parameters, can be used to display any text in the given
	style without repeated redefinitions.
-*----------------------------------------------------------------------------*/

stock Style:TD_Create(Float:x = 0.0, Float:y = 0.0, Float:letterX = 0.48, Float:letterY = 1.12, Float:textX = 1280.0, Float:textY = 1280.0, colour = 0xE1E1E1FF, boxColour = 0x80808080, bgColour = 0x000000FF, shadow = 2, outline = 0, align = _:td_align_none, font = 1, bool:proportional = false, bool:box = false, time = 0, name[] = "\1")
{
	new
		Style:i;
	while (i < MAX_TEXT_DRAW_STYLES && YSI_g_sTDData[i][E_TD_DATA_NAME][0])
	{
		i++;
	}
	if (i == MAX_TEXT_DRAW_STYLES)
	{
		return MAX_TEXT_DRAW_STYLES;
	}
	strcpy(YSI_g_sTDData[i][E_TD_DATA_NAME], name, MAX_XML_ENTRY_NAME);
	YSI_g_sTDData[i][E_TD_DATA_HASH] = bernstein(name);
	YSI_g_sTDData[i][E_TD_DATA_X] = x;
	YSI_g_sTDData[i][E_TD_DATA_Y] = y;
	YSI_g_sTDData[i][E_TD_DATA_LX] = letterX;
	YSI_g_sTDData[i][E_TD_DATA_LY] = letterY;
	YSI_g_sTDData[i][E_TD_DATA_TX] = textX;
	YSI_g_sTDData[i][E_TD_DATA_TY] = textY;
	YSI_g_sTDData[i][E_TD_DATA_COLOUR] = colour;
	YSI_g_sTDData[i][E_TD_DATA_BITS] =
		((box) ? (e_TD_BITS_BOX) : (e_TD_BITS:0)) |
		((proportional) ? (e_TD_BITS_PROP) : (e_TD_BITS:0)) |
		(e_TD_BITS:(shadow) & e_TD_BITS_SHADOW) |
		(e_TD_BITS:(outline << 8) & e_TD_BITS_OUTLINE) |
		(e_TD_BITS:(align << 16) & e_TD_BITS_ALIGN) |
		(e_TD_BITS:(font << 20) & e_TD_BITS_FONT);
	YSI_g_sTDData[i][E_TD_DATA_BOX] = boxColour;
	YSI_g_sTDData[i][E_TD_DATA_BG] = bgColour;
	YSI_g_sTDData[i][E_TD_DATA_TIME] = time;
	YSI_g_sTDData[i][E_TD_DATA_USE] = TEXT_DRAW_NO_NEXT;
	return Style:i;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Create
Params:
	Style:styleID - Style to clone.
	name[] - Name of the new style.
Return:
	-
Notes:
	Copies a text draw style and returns the new handle.
-*----------------------------------------------------------------------------*/

stock Style:TD_Clone(Style:styleID, name[] = "\1")
{
	if (!TD_IsValid(styleID))
	{
		return MAX_TEXT_DRAW_STYLES;
	}
	new
		Style:i;
	while (i < MAX_TEXT_DRAW_STYLES && YSI_g_sTDData[i][E_TD_DATA_NAME][0])
	{
		i++;
	}
	if (i == MAX_TEXT_DRAW_STYLES)
	{
		return MAX_TEXT_DRAW_STYLES;
	}
	strcpy(YSI_g_sTDData[i][E_TD_DATA_NAME], name, MAX_XML_ENTRY_NAME);
	YSI_g_sTDData[i][E_TD_DATA_HASH] = bernstein(name);
	YSI_g_sTDData[i][E_TD_DATA_X] = YSI_g_sTDData[styleID][E_TD_DATA_X];
	YSI_g_sTDData[i][E_TD_DATA_Y] = YSI_g_sTDData[styleID][E_TD_DATA_Y];
	YSI_g_sTDData[i][E_TD_DATA_LX] = YSI_g_sTDData[styleID][E_TD_DATA_LX];
	YSI_g_sTDData[i][E_TD_DATA_LY] = YSI_g_sTDData[styleID][E_TD_DATA_LY];
	YSI_g_sTDData[i][E_TD_DATA_TX] = YSI_g_sTDData[styleID][E_TD_DATA_TX];
	YSI_g_sTDData[i][E_TD_DATA_TY] = YSI_g_sTDData[styleID][E_TD_DATA_TY];
	YSI_g_sTDData[i][E_TD_DATA_COLOUR] = YSI_g_sTDData[styleID][E_TD_DATA_COLOUR];
	YSI_g_sTDData[i][E_TD_DATA_BITS] = YSI_g_sTDData[styleID][E_TD_DATA_BITS];
	YSI_g_sTDData[i][E_TD_DATA_BOX] = YSI_g_sTDData[styleID][E_TD_DATA_BOX];
	YSI_g_sTDData[i][E_TD_DATA_BG] = YSI_g_sTDData[styleID][E_TD_DATA_BG];
	YSI_g_sTDData[i][E_TD_DATA_TIME] = YSI_g_sTDData[styleID][E_TD_DATA_TIME];
	YSI_g_sTDData[i][E_TD_DATA_USE] = TEXT_DRAW_NO_NEXT;
	return Style:i;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Name
Params:
	Style:styleID - Style to modify.
	name[] - Name to give the style.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_Name(Style:styleID, name[])
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	strcpy(YSI_g_sTDData[styleID][E_TD_DATA_NAME], name, MAX_XML_ENTRY_NAME);
	YSI_g_sTDData[styleID][E_TD_DATA_HASH] = bernstein(name);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_TextPosition
Params:
	Text:textID - Text to modify.
	Float:x - New horizontal position.
	Float:y - New vertical position.
Return:
	-
Notes:
	Moves a single bit of text, not all with a style.
-*----------------------------------------------------------------------------*/

stock TD_TextPosition(Text:textID, Float:x, Float:y)
{
	if (textID < Text:0 || textID >= Text:MAX_TEXT_DRAWS)
	{
		return 0;
	}
	YSI_g_sTDDisplay[textID][E_TD_DISPLAY_X] = x;
	YSI_g_sTDDisplay[textID][E_TD_DISPLAY_Y] = y;
	TD_UpdateOne(textID, YSI_g_sTDDisplay[textID][E_TD_DISPLAY_STYLE]);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_TextXPos
Params:
	Text:textID - Text to modify.
	Float:x - New horizontal position.
Return:
	-
Notes:
	Moves a single bit of text, not all with a style.
-*----------------------------------------------------------------------------*/

stock TD_TextXPos(Text:textID, Float:x)
{
	if (textID < Text:0 || textID >= Text:MAX_TEXT_DRAWS)
	{
		return 0;
	}
	YSI_g_sTDDisplay[textID][E_TD_DISPLAY_X] = x;
	TD_UpdateOne(textID, YSI_g_sTDDisplay[textID][E_TD_DISPLAY_STYLE]);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_TextYPos
Params:
	Text:textID - Text to modify.
	Float:y - New vertical position.
Return:
	-
Notes:
	Moves a single bit of text, not all with a style.
-*----------------------------------------------------------------------------*/

stock TD_TextYPos(Text:textID, Float:y)
{
	if (textID < Text:0 || textID >= Text:MAX_TEXT_DRAWS)
	{
		return 0;
	}
	YSI_g_sTDDisplay[textID][E_TD_DISPLAY_Y] = y;
	TD_UpdateOne(textID, YSI_g_sTDDisplay[textID][E_TD_DISPLAY_STYLE]);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_StylePosition
Params:
	Style:styleID - Style to modify.
	Float:x - New horizontal position.
	Float:y - New vertical position.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	Update is default false to not modify moved texts.
-*----------------------------------------------------------------------------*/

stock TD_StylePosition(Style:styleID, Float:x, Float:y, bool:update = false)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_X] = x;
	YSI_g_sTDData[styleID][E_TD_DATA_Y] = y;
	if (update) TD_Update(styleID, true);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_StyleXPos
Params:
	Style:styleID - Style to modify.
	Float:x - New horizontal position.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	Update is default false to not modify moved texts.
-*----------------------------------------------------------------------------*/

stock TD_StyleXPos(Style:styleID, Float:x, bool:update = false)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_X] = x;
	if (update) TD_Update(styleID, true);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_StyleYPos
Params:
	Style:styleID - Style to modify.
	Float:y - New vertical position.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	Update is default false to not modify moved texts.
-*----------------------------------------------------------------------------*/

stock TD_StyleYPos(Style:styleID, Float:y, bool:update = false)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_Y] = y;
	if (update) TD_Update(styleID, true);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_LetterSize
Params:
	Style:styleID - Style to modify.
	Float:x - New letter width.
	Float:y - New letter height.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_LetterSize(Style:styleID, Float:x, Float:y, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_LX] = x;
	YSI_g_sTDData[styleID][E_TD_DATA_LY] = y;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_LetterX
Params:
	Style:styleID - Style to modify.
	Float:x - New letter width.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_LetterX(Style:styleID, Float:x, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_LX] = x;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_LetterY
Params:
	Style:styleID - Style to modify.
	Float:y - New letter height.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_LetterY(Style:styleID, Float:y, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_LY] = y;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_TextSize
Params:
	Style:styleID - Style to modify.
	Float:x - New text width.
	Float:y - New text height.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_TextSize(Style:styleID, Float:x, Float:y, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_TX] = x;
	YSI_g_sTDData[styleID][E_TD_DATA_TY] = y;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_TextX
Params:
	Style:styleID - Style to modify.
	Float:x - New text width.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_TextX(Style:styleID, Float:x, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_TX] = x;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_TextY
Params:
	Style:styleID - Style to modify.
	Float:y - New text height.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_TextY(Style:styleID, Float:y, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_TY] = y;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Alignment
Params:
	Style:styleID - Style to modify.
	alignment - Where to align the text in it's box.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	Designed to take ta_align enum values and numbers.
-*----------------------------------------------------------------------------*/

stock TD_Alignment(Style:styleID, alignment = _:td_align_none, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_BITS] = (YSI_g_sTDData[styleID][E_TD_DATA_BITS] & ~e_TD_BITS_ALIGN) | (e_TD_BITS:(alignment << 16) & e_TD_BITS_ALIGN);
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Colour
Params:
	Style:styleID - Style to modify.
	colour - New text colour.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define TD_Color TD_Colour

stock TD_Colour(Style:styleID, colour, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_COLOUR] = colour;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_UseBox
Params:
	Style:styleID - Style to modify.
	bool:use - Wether or not to show a box round the text.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_UseBox(Style:styleID, bool:use, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	if (use) YSI_g_sTDData[styleID][E_TD_DATA_BITS] |= e_TD_BITS_BOX;
	else YSI_g_sTDData[styleID][E_TD_DATA_BITS] &= ~e_TD_BITS_BOX;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_BoxColour
Params:
	Style:styleID - Style to modify.
	colour - New box colour.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define TD_BoxColor TD_BoxColour

stock TD_BoxColour(Style:styleID, colour, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_BOX] = colour;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_SetShadow
Params:
	Style:styleID - Style to modify.
	size - Size of the letter shadow,
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_SetShadow(Style:styleID, size, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_BITS] = (YSI_g_sTDData[styleID][E_TD_DATA_BITS] & ~e_TD_BITS_SHADOW) | (e_TD_BITS:(size) & e_TD_BITS_SHADOW);
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_SetOutline
Params:
	Style:styleID - Style to modify.
	size - Size of the letter outline.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_SetOutline(Style:styleID, size, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_BITS] = (YSI_g_sTDData[styleID][E_TD_DATA_BITS] & ~e_TD_BITS_OUTLINE) | (e_TD_BITS:(size << 8) & e_TD_BITS_OUTLINE);
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_BackgroundColour
Params:
	Style:styleID - Style to modify.
	colour - New background (outline/shadow) colour.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define TD_BackgroundColor TD_BackgroundColour

stock TD_BackgroundColour(Style:styleID, colour, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_BG] = colour;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Font
Params:
	Style:styleID - Style to modify.
	font - New text font style.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_Font(Style:styleID, font, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_BITS] = (YSI_g_sTDData[styleID][E_TD_DATA_BITS] & ~e_TD_BITS_FONT) | (e_TD_BITS:(font << 20) & e_TD_BITS_FONT);
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_SetProportional
Params:
	Style:styleID - Style to modify.
	bool:set - Wether to make the letters proportional or not.
	bool:update - Wether to update the appearence for players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_SetProportional(Style:styleID, bool:set, bool:update = true)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	if (set) YSI_g_sTDData[styleID][E_TD_DATA_BITS] |= e_TD_BITS_PROP;
	else YSI_g_sTDData[styleID][E_TD_DATA_BITS] &= ~e_TD_BITS_PROP;
	if (update) TD_Update(styleID);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_SetTime
Params:
	Style:styleID - Style to modify.
	time - New time for the text to display for.
Return:
	-
Notes:
	Doesn't update existing timed texts, just new ones.
-*----------------------------------------------------------------------------*/

stock TD_SetTime(Style:styleID, time)
{
	if (!TD_IsValid(styleID))
	{
		return 0;
	}
	YSI_g_sTDData[styleID][E_TD_DATA_TIME] = time;
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	Text:TD_Render
Params:
	text[] - String to output.
	Style:id - Text draw style to render the text in.
	Text:slot - the slot the text is stored in.
Return:
	TextDraw id.
Notes:
	Basically the application layer, creates a text_draw with the
	saved data.
-*----------------------------------------------------------------------------*/

static stock Text:TD_Render(text[], Style:id, Text:slot)
{
	new
		Text:textDraw = TextDrawCreate(YSI_g_sTDDisplay[slot][E_TD_DISPLAY_X], YSI_g_sTDDisplay[slot][E_TD_DISPLAY_Y], text);
	if (textDraw != Text:INVALID_TEXT_DRAW)
	{
		new
			e_TD_BITS:bits = YSI_g_sTDData[id][E_TD_DATA_BITS];
		TextDrawLetterSize(textDraw, YSI_g_sTDData[id][E_TD_DATA_LX], YSI_g_sTDData[id][E_TD_DATA_LY]);
		TextDrawTextSize(textDraw, YSI_g_sTDData[id][E_TD_DATA_TX], YSI_g_sTDData[id][E_TD_DATA_TY]);
		TextDrawAlignment(textDraw, _:(bits & e_TD_BITS_ALIGN) >> 16);
		TextDrawColor(textDraw, YSI_g_sTDData[id][E_TD_DATA_COLOUR]);
		TextDrawUseBox(textDraw, (bits & e_TD_BITS_BOX) ? 1 : 0);
		TextDrawBoxColor(textDraw, YSI_g_sTDData[id][E_TD_DATA_BOX]);
		TextDrawSetShadow(textDraw, _:(bits & e_TD_BITS_SHADOW));
		TextDrawSetOutline(textDraw, _:(bits & e_TD_BITS_OUTLINE) >> 8);
		TextDrawBackgroundColor(textDraw, YSI_g_sTDData[id][E_TD_DATA_BG]);
		TextDrawFont(textDraw, _:(bits & e_TD_BITS_FONT) >> 20);
		TextDrawSetProportional(textDraw, (bits & e_TD_BITS_PROP) ? 1 : 0);
	}
	return textDraw;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Update
Params:
	Style:id - Style to update for players.
	bool:pos - Wether or not to update children's positions.
Return:
	-
Notes:
	Loops through all texts displayed using the current render
	style and updates their real display.
-*----------------------------------------------------------------------------*/

stock TD_Update(Style:id, bool:pos = false)
{
	if (!TD_IsValid(id))
	{
		return;
	}
	new
		Text:next = YSI_g_sTDData[id][E_TD_DATA_USE],
		Float:x,
		Float:y;
	if (pos)
	{
		x = YSI_g_sTDData[id][E_TD_DATA_X],
		y = YSI_g_sTDData[id][E_TD_DATA_Y];
	}
	while (next != TEXT_DRAW_NO_NEXT)
	{
		if (pos)
		{
			YSI_g_sTDDisplay[next][E_TD_DISPLAY_X] = x;
			YSI_g_sTDDisplay[next][E_TD_DISPLAY_Y] = y;
		}
		TD_UpdateOne(next, id);
		next = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
	}
}

/*----------------------------------------------------------------------------*-
Function:
	TD_UpdateOne
Params:
	Text:slot - Text to update.
	Style:id - Style to use.
Return:
	-
Notes:
	Updates a single text's appearence.
-*----------------------------------------------------------------------------*/

stock TD_UpdateOne(Text:slot, Style:id)
{
	TextDrawDestroy(YSI_g_sTDDisplay[slot][E_TD_DISPLAY_REAL]);
	new
		Text:textDraw = TD_Render(YSI_g_sTDDisplay[slot][E_TD_DISPLAY_TEXT], id, slot);
	YSI_g_sTDDisplay[slot][E_TD_DISPLAY_REAL] = textDraw;
	if (textDraw != Text:INVALID_TEXT_DRAW)
	{
		new
			index = _:slot / cellbits,
			mod = 1 << (_:slot % cellbits);
		foreach (Player, playerid)
		{
			if (YSI_g_sPlayerDraws[playerid][index] & Bit:mod)
			{
				TextDrawShowForPlayer(playerid, textDraw);
			}
		}
	}
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Delete
Params:
	styleId - Text draw style ID you want to remove.
Return:
	-
Notes:
	Just nulls the name to remove it's active marker.
-*----------------------------------------------------------------------------*/

stock TD_Delete(Style:styleId)
{
	if (!TD_IsValid(styleId))
	{
		return 0;
	}
	new
		Text:next = YSI_g_sTDData[styleId][E_TD_DATA_USE],
		Text:last = TEXT_DRAW_NO_NEXT;
	while (next != TEXT_DRAW_NO_NEXT)
	{
		TextDrawDestroy(YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL]);
		YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL] = Text:INVALID_TEXT_DRAW;
		new
			index = _:next / cellbits,
			mod = ~(1 << (_:next % cellbits));
		foreach (Player, playerid)
		{
			if (YSI_g_sTDTimers[next][playerid])
			{
				KillTimer(YSI_g_sTDTimers[next][playerid]);
			}
			YSI_g_sTDTimers[next][playerid] = 0;
			YSI_g_sPlayerDraws[playerid][index] &= Bit:mod;
		}
		last = next;
		next = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
	}
	if (last != TEXT_DRAW_NO_NEXT)
	{
		YSI_g_sTDDisplay[last][E_TD_DISPLAY_NEXT] = YSI_g_sUnused;
		YSI_g_sUnused = YSI_g_sTDData[styleId][E_TD_DATA_USE];
	}
	YSI_g_sTDData[styleId][E_TD_DATA_USE] = TEXT_DRAW_NO_NEXT;
	YSI_g_sTDData[styleId][E_TD_DATA_NAME][0] = '\0';
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Destroy
Params:
	Text:textDraw - Text draw to destroy safely.
Return:
	-
Notes:
	Optimised for multiple people.
-*----------------------------------------------------------------------------*/

stock TD_Destroy(Text:textDraw)
{
	if (textDraw >= Text:0 && textDraw < Text:MAX_TEXT_DRAWS)
	{
		new
			Style:style = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_STYLE],
			Text:next = YSI_g_sTDData[style][E_TD_DATA_USE],
			Text:last = TEXT_DRAW_NO_NEXT;
		while (next != TEXT_DRAW_NO_NEXT && next != textDraw)
		{
			last = next;
			next = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
		}
		if (last == TEXT_DRAW_NO_NEXT) YSI_g_sTDData[style][E_TD_DATA_USE] = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT];
		else YSI_g_sTDDisplay[last][E_TD_DISPLAY_NEXT] = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT];
		YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT] = YSI_g_sUnused;
		YSI_g_sUnused = textDraw;
		new
			Text:real = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL];
		YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL] = Text:INVALID_TEXT_DRAW;
		if (real != Text:INVALID_TEXT_DRAW)
		{
			new
				index = _:textDraw / cellbits,
				mod = ~(1 << (_:textDraw % cellbits));
			TextDrawDestroy(real);
			foreach (Player, playerid)
			{
				if (YSI_g_sTDTimers[textDraw][playerid])
				{
					KillTimer(YSI_g_sTDTimers[textDraw][playerid]);
				}
				YSI_g_sTDTimers[textDraw][playerid] = 0;
				YSI_g_sPlayerDraws[playerid][index] &= Bit:mod;
			}
			return 1;
		}
	}
	return 0;
}

/*----------------------------------------------------------------------------*-
Function:
	Text:TD_Display
Params:
	text[] - Text to display onscreen.
	Style:id - Style to use to style text.
Return:
	Internal Text: id, not actual text draw's id.
Notes:
	Generates a text draw for to display to people.
-*----------------------------------------------------------------------------*/

stock Text:TD_Display(text[], Style:id)
{
	if (YSI_g_sUnused == TEXT_DRAW_NO_NEXT)
	{
		return Text:INVALID_TEXT_DRAW;
	}
	if (!TD_IsValid(id))
	{
		return Text:INVALID_TEXT_DRAW;
	}
	YSI_g_sTDDisplay[YSI_g_sUnused][E_TD_DISPLAY_X] = YSI_g_sTDData[id][E_TD_DATA_X];
	YSI_g_sTDDisplay[YSI_g_sUnused][E_TD_DISPLAY_Y] = YSI_g_sTDData[id][E_TD_DATA_Y];
	YSI_g_sTDDisplay[YSI_g_sUnused][E_TD_DISPLAY_STYLE] = id;
	new
		Text:textDraw = TD_Render(text, id, YSI_g_sUnused);
	if (textDraw == Text:INVALID_TEXT_DRAW)
	{
		return Text:INVALID_TEXT_DRAW;
	}
	YSI_g_sTDDisplay[YSI_g_sUnused][E_TD_DISPLAY_REAL] = textDraw;
	strcpy(YSI_g_sTDDisplay[YSI_g_sUnused][E_TD_DISPLAY_TEXT], text, MAX_TEXT_DRAW_LINE);
	textDraw = Text:YSI_g_sUnused;
	YSI_g_sUnused = YSI_g_sTDDisplay[YSI_g_sUnused][E_TD_DISPLAY_NEXT];
	YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT] = YSI_g_sTDData[id][E_TD_DATA_USE];
	YSI_g_sTDData[id][E_TD_DATA_USE] = textDraw;
	YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] = 0;
	return textDraw;
}

/*----------------------------------------------------------------------------*-
Function:
	Text:TD_DisplayHashed
Params:
	text[] - Text to display.
	hash - Hashed style name for styling.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock Text:TD_DisplayHashed(text[], hash)
{
	new
		Style:id = TD_GetID(hash);
	if (id != MAX_TEXT_DRAW_STYLES)
	{
		return TD_Display(text, id);
	}
	return Text:INVALID_TEXT_DRAW;
}

/*----------------------------------------------------------------------------*-
Function:
	Text:TD_DisplayNamed
Params:
	text[] - Text to display.
	style[] - Named style to display the text with.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock Text:TD_DisplayNamed(text[], style[])
{
	return TD_DisplayHashed(text, bernstein(style));
}

/*----------------------------------------------------------------------------*-
Function:
	TD_ShowForPlayer
Params:
	playerid - Player to show the text to.
	Text:textDraw - ID of the text to show.
Return:
	-
Notes:
	Now destroys any existing text draws using the same style in
	the same place to avoid overlaps.
-*----------------------------------------------------------------------------*/

stock TD_ShowForPlayer(playerid, Text:textDraw)
{
	if (textDraw >= Text:0 && textDraw < Text:MAX_TEXT_DRAWS && !Bit_GetBit(YSI_g_sPlayerDraws[playerid], _:textDraw))
	{
		new
			Text:real = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL];
		if (real != Text:INVALID_TEXT_DRAW)
		{
			new
				Style:style = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_STYLE],
				Text:next = YSI_g_sTDData[style][E_TD_DATA_USE],
				Text:last = TEXT_DRAW_NO_NEXT,
				Text:cur = TEXT_DRAW_NO_NEXT,
				Float:x = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_X],
				Float:y = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_Y],
				count,
				shown = 0;
			while (next != TEXT_DRAW_NO_NEXT)
			{
				if (Bit_Get(YSI_g_sPlayerDraws[playerid], _:next))
				{
					if (next == textDraw)
					{
						shown = 1;
					}
					else if (x == YSI_g_sTDDisplay[next][E_TD_DISPLAY_X] && y == YSI_g_sTDDisplay[next][E_TD_DISPLAY_Y])
					{
						count = YSI_g_sTDDisplay[next][E_TD_DISPLAY_LIFE];
						cur = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
						if (count == 0x80000001)
						{
							if (last == TEXT_DRAW_NO_NEXT)
							{
								YSI_g_sTDData[style][E_TD_DATA_USE] = cur;
							}
							else
							{
								YSI_g_sTDDisplay[last][E_TD_DISPLAY_NEXT] = cur;
							}
							YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT] = YSI_g_sUnused;
							YSI_g_sUnused = next;
							new
								Text:temp = YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL];
							YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL] = Text:INVALID_TEXT_DRAW;
							if (temp != Text:INVALID_TEXT_DRAW)
							{
								TextDrawDestroy(temp);
							}
						}
						else
						{
							YSI_g_sTDDisplay[next][E_TD_DISPLAY_LIFE] = (count & 0x80000000) | ((count & 0x7FFFFFFF) - 1);
							TextDrawHideForPlayer(playerid, YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL]);
							last = next;
						}
						Bit_Vet(YSI_g_sPlayerDraws[playerid], _:next);
						next = cur;
						continue;
					}
				}
				last = next;
				next = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
			}
			if (!shown)
			{
				TextDrawShowForPlayer(playerid, real);
				Bit_Let(YSI_g_sPlayerDraws[playerid], _:textDraw);
				count = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE];
				YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] = (count & 0x80000000) | ((count & 0x7FFFFFFF) + 1);
				new
					time = YSI_g_sTDData[YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_STYLE]][E_TD_DATA_TIME];
				if (time)
				{
					if (YSI_g_sTDTimers[textDraw][playerid])
					{
						KillTimer(YSI_g_sTDTimers[textDraw][playerid]);
					}
					YSI_g_sTDTimers[textDraw][playerid] = SetTimerEx("TD_HideForPlayer", time, 0, "ii", playerid, _:textDraw);
				}
			}
			return 1;
		}
	}
	return 0;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_HideForPlayer
Params:
	playerid - Player to hide the text for.
	Text:textDraw - Text to hide.
Return:
	-
Notes:
	Public so the timer can call it to hide texts with a time set.
-*----------------------------------------------------------------------------*/

public TD_HideForPlayer(playerid, Text:textDraw)
{
	if (IsPlayerConnected(playerid))
	{
		if (textDraw >= Text:0 && textDraw < Text:MAX_TEXT_DRAWS && Bit_GetBit(YSI_g_sPlayerDraws[playerid], _:textDraw))
		{
			if (YSI_g_sTDTimers[textDraw][playerid])
			{
				KillTimer(YSI_g_sTDTimers[textDraw][playerid]);
			}
			YSI_g_sTDTimers[textDraw][playerid] = 0;
			new
				count = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE];
			if (count == 0x80000001)
			{
				TD_Destroy(Text:textDraw);
			}
			else
			{
				Bit_Vet(YSI_g_sPlayerDraws[playerid], _:textDraw);
				YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] = (count & 0x80000000) | ((count & 0x7FFFFFFF) - 1);
				TextDrawHideForPlayer(playerid, YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL]);
			}
			return 1;
		}
	}
	return 0;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_ShowForAll
Params:
	Text:textDraw - Text to show to all players.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock TD_ShowForAll(Text:textDraw)
{
	if (textDraw >= Text:0 && textDraw < Text:MAX_TEXT_DRAWS)
	{
		new
			Text:real = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL];
		if (real != Text:INVALID_TEXT_DRAW)
		{
			new
				index,
				mod,
				time = YSI_g_sTDData[YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_STYLE]][E_TD_DATA_TIME],
				count,
				Float:x = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_X],
				Float:y = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_Y],
				Style:style = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_STYLE],
				Text:next = YSI_g_sTDData[style][E_TD_DATA_USE],
				Text:last = TEXT_DRAW_NO_NEXT,
				Text:cur = TEXT_DRAW_NO_NEXT,
				Text:thisLast = TEXT_DRAW_NO_NEXT;
			while (next != TEXT_DRAW_NO_NEXT)
			{
				if (next == textDraw)
				{
					thisLast = last;
					last = next;
					next = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
				}
				else if (x == YSI_g_sTDDisplay[next][E_TD_DISPLAY_X] && y == YSI_g_sTDDisplay[next][E_TD_DISPLAY_Y])
				{
					count = YSI_g_sTDDisplay[next][E_TD_DISPLAY_LIFE];
					cur = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
					if (count & 0x80000000)
					{
						if (last == TEXT_DRAW_NO_NEXT)
						{
							YSI_g_sTDData[style][E_TD_DATA_USE] = cur;
						}
						else
						{
							YSI_g_sTDDisplay[last][E_TD_DISPLAY_NEXT] = cur;
						}
						YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT] = YSI_g_sUnused;
						YSI_g_sUnused = next;
						new
							Text:temp = YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL];
						YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL] = Text:INVALID_TEXT_DRAW;
						if (temp != Text:INVALID_TEXT_DRAW)
						{
							TextDrawDestroy(temp);
						}
					}
					else
					{
						TextDrawHideForAll(YSI_g_sTDDisplay[next][E_TD_DISPLAY_REAL]);
						last = next;
					}
					index = _:next / cellbits;
					mod = 1 << (_:next % cellbits);
					foreach (Player, playerid)
					{
						YSI_g_sPlayerDraws[playerid][index] &= Bit:mod;
					}
					next = cur;
				}
				else
				{
					last = next;
					next = YSI_g_sTDDisplay[next][E_TD_DISPLAY_NEXT];
				}
			}
			TextDrawShowForAll(real);
			count = 0;
			index = _:textDraw / cellbits;
			mod = 1 << (_:textDraw % cellbits);
			foreach (Player, playerid)
			{
				YSI_g_sPlayerDraws[playerid][index] |= Bit:mod;
				count++;
				if (time)
				{
					if (YSI_g_sTDTimers[textDraw][playerid])
					{
						KillTimer(YSI_g_sTDTimers[textDraw][playerid]);
					}
					YSI_g_sTDTimers[textDraw][playerid] = SetTimerEx("TD_HideForPlayer", time, 0, "ii", playerid, _:textDraw);
				}
			}
			if (count)
			{
				YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] = (YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] & 0x80000000) | count;
			}
			else if (YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] & 0x80000000)
			{
				if (thisLast == TEXT_DRAW_NO_NEXT)
				{
					YSI_g_sTDData[style][E_TD_DATA_USE] = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT];
				}
				else
				{
					YSI_g_sTDDisplay[thisLast][E_TD_DISPLAY_NEXT] = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT];
				}
				YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_NEXT] = YSI_g_sUnused;
				YSI_g_sUnused = textDraw;
				TextDrawDestroy(real);
				YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL] = Text:INVALID_TEXT_DRAW;
			}
			return 1;
		}
	}
	return 0;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_HideForAll
Params:
	Text:textDraw - Text to hide from all players.
Return:
	-
Notes:
	Destroys the real text draw if marked for garbage collection.
-*----------------------------------------------------------------------------*/

stock TD_HideForAll(Text:textDraw)
{
	if (textDraw >= Text:0 && textDraw < Text:MAX_TEXT_DRAWS)
	{
		new
			Text:real = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL];
		if (real != Text:INVALID_TEXT_DRAW)
		{
			if (YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] & 0x80000000)
			{
				TD_Destroy(Text:textDraw);
			}
			else
			{
				YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] = 0;
				new
					index = _:textDraw / cellbits,
					mod = 1 << (_:textDraw % cellbits),
					inv = ~mod;
				TextDrawHideForAll(real);
				foreach (Player, playerid)
				{
					if (YSI_g_sTDTimers[textDraw][playerid])
					{
						KillTimer(YSI_g_sTDTimers[textDraw][playerid]);
					}
					YSI_g_sTDTimers[textDraw][playerid] = 0;
					YSI_g_sPlayerDraws[playerid][index] &= Bit:inv;
				}
			}
			return 1;
		}
	}
	return 0;
}

/*----------------------------------------------------------------------------*-
Function:
	TD_OnPlayerDisconnect
Params:
	playerid - Player who left.
	reason - Why they left.
Return:
	-
Notes:
	Required to fix bugs in the textdraw system by hiding all
	visible ones.
-*----------------------------------------------------------------------------*/

TD_OnPlayerDisconnect(playerid, reason)
{
	for (new i = 0; i < Bit_Bits(MAX_TEXT_DRAWS); i++)
	{
		if (YSI_g_sPlayerDraws[playerid][i])
		{
			for (new j = 0, Bit:k = Bit:1; j < cellbits; j++, k <<= Bit:1)
			{
				if (YSI_g_sPlayerDraws[playerid][i] & k)
				{
					new
						Text:textDraw = Text:((i * cellbits) + j),
						count = YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE];
					if (count == 0x80000001)
					{
						TD_Destroy(Text:textDraw);
					}
					else
					{
						Bit_Vet(YSI_g_sPlayerDraws[playerid], _:textDraw);
						if (YSI_g_sTDTimers[textDraw][playerid])
						{
							KillTimer(YSI_g_sTDTimers[textDraw][playerid]);
						}
						YSI_g_sTDTimers[textDraw][playerid] = 0;
						YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] = (count & 0x80000000) | ((count & 0x7FFFFFFF) - 1);
						TextDrawHideForPlayer(playerid, YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_REAL]);
					}
				}
			}
			YSI_g_sPlayerDraws[playerid][i] = Bit:0;
		}
	}
	return 1;
	#pragma unused reason
}

/*----------------------------------------------------------------------------*-
Function:
	TD_Garbage
Params:
	Text:textDraw - Text to mark as garbage.
Return:
	-
Notes:
	Tells the system to remove a text draw when no-one can see it
	anymore to free up text draw slots.
-*----------------------------------------------------------------------------*/

stock TD_Garbage(Text:textDraw)
{
	if (textDraw >= Text:0 && textDraw < Text:MAX_TEXT_DRAWS)
	{
		if (YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] & 0x7FFFFFFF)
		{
			YSI_g_sTDDisplay[textDraw][E_TD_DISPLAY_LIFE] |= 0x80000000;
		}
		else
		{
			TD_Destroy(textDraw);
		}
	}
}
