/*----------------------------------------------------------------------------*-
					=============================
					Y Sever Includes - Class Core
					=============================
Description:
	Allows greater control over classes so not everyone has every class.  Uses
	a form of compression for locations.
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:
	05/08/07:
		Fixed a few bugs with repeated selection.
	04/08/07:
		First version.
Functions:
	Public:
		-
	Core:
		Class_Class - Sets up the system.
		Class_OnPlayerRequestSpawn - Called when a player requests a spawn.
		Class_OnPlayerRequestClass - Called when a player requests a class.
		Class_OnPlayerConnect - Called when a player connects.
	Stock:
		Class_SetPlayer - Sets whether or not a player can use a class.
		Class_Disable - Disables a class.
		Class_Enable - Enables a disabled class.
		Class_AddForGroup - Adds a class to the system for only one group.
		Class_Add - Adds a class to the system.
	Static:
		-
	Inline:
		Class_IsActive - Checks a class is active.
		Class_Enabled - Checks a class is enabled.
		Class_IsValid - Checks a class is valid.
		Class_X - Gets a classes x position.
		Class_Y - Gets a classes y position.
		Class_Z - Gets a classes z position.
		Class_A - Gets a classes angle.
		Class_Skin - Gets a classes skin.
	API:
		-
Callbacks:
	-
Definitions:
	MAX_CLASSES - Maximum number of classes storeable by the system.
	CLASS_LEFT - Flag for last internal class viewed.
	CLASS_MIDDLE - Flag for last internal class viewed.
	CLASS_RIGHT - Flag for last internal class viewed.
Enums:
	e_CLASS_FLAGS - Small data for individual classes.
	E_CLASS - Class data structure.
Macros:
	-
Tags:
	-
Variables:
	Global:
		-
	Static:
		YSI_g_sClasses - Data for classes.
		YSI_g_sPlayerClass - Player's current classes.
		YSI_g_sLeft - Handle for the first internal class.
		YSI_g_sMiddle - Handle for the second internal class.
		YSI_g_sRight - Handle for the third internal class.
		YSI_g_sClassCount - Number of classes stored.
Commands:
	-
Compile options:
	-
Operators:
	-
-*----------------------------------------------------------------------------*/

#if !defined MAX_CLASSES
	#define MAX_CLASSES 256
#endif

enum e_CLASS_FLAGS (<<= 1)
{
	e_CLASS_FLAGS_SKIN   = 0x0000FFFF,
	e_CLASS_FLAGS_ACTIVE = 0x01000000,
	e_CLASS_FLAGS_ENABLED,
	e_CLASS_FLAGS_DEFAULT
}

enum E_CLASS
{
	e_CLASS_FLAGS:E_CLASS_FLAGS,
	E_CLASS_XY,
	E_CLASS_ZA,
	E_CLASS_WEAPONS[3],
	Bit:E_CLASS_PLAYERS[PLAYER_BIT_ARRAY],
	E_CLASS_GROUP
}

#define CLASS_LEFT 0x40000000
#define CLASS_MIDDLE 0x20000000
#define CLASS_RIGHT 0x10000000

static
	YSI_g_sClasses[MAX_CLASSES][E_CLASS],
	YSI_g_sPlayerGroup[MAX_PLAYERS],
	YSI_g_sPlayerClass[MAX_PLAYERS],
	YSI_g_sLeft,
	YSI_g_sMiddle,
	YSI_g_sRight,
	YSI_g_sClassCount;

/*----------------------------------------------------------------------------*-
Function:
	Class_IsActive
Params:
	classid - Class to check.
Return:
	-
Notes:
	Checks if a class is currently in use.
-*----------------------------------------------------------------------------*/

#define Class_IsActive(%1) \
	(YSI_g_sClasses[(%1)][E_CLASS_FLAGS] & e_CLASS_FLAGS_ACTIVE)

/*----------------------------------------------------------------------------*-
Function:
	Class_Enabled
Params:
	classid - Class to check.
Return:
	-
Notes:
	Checks if a class is currently available for viewing.
-*----------------------------------------------------------------------------*/

#define Class_Enabled(%1) \
	(YSI_g_sClasses[(%1)][E_CLASS_FLAGS] & e_CLASS_FLAGS_ENABLED)

/*----------------------------------------------------------------------------*-
Function:
	Class_IsValid
Params:
	classid - Class to check.
Return:
	-
Notes:
	Checks if a number is a valid classid and active.
-*----------------------------------------------------------------------------*/

#define Class_IsValid(%1) \
	((%1) >= 0 && (%1) < MAX_CLASSES && Class_IsActive((%1)))

/*----------------------------------------------------------------------------*-
Function:
	Class_X
Params:
	classid - Class to get X location for.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define Class_X(%1) \
	(float(YSI_g_sClasses[(%1)][E_CLASS_XY] >> 16) / 10.0)

/*----------------------------------------------------------------------------*-
Function:
	Class_Y
Params:
	classid - Class to get Y location for.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define Class_Y(%1) \
	(float(YSI_g_sClasses[(%1)][E_CLASS_XY] & 0xFFFF) / 10.0)

/*----------------------------------------------------------------------------*-
Function:
	Class_Z
Params:
	classid - Class to get Z location for.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define Class_Z(%1) \
	(float(YSI_g_sClasses[(%1)][E_CLASS_ZA] >> 16) / 10.0)

/*----------------------------------------------------------------------------*-
Function:
	Class_A
Params:
	classid - Class to get angle for.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define Class_A(%1) \
	(float(YSI_g_sClasses[(%1)][E_CLASS_ZA] & 0xFFFF) / 10.0)

/*----------------------------------------------------------------------------*-
Function:
	Class_Skin
Params:
	classid - Class to get skin for.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

#define Class_Skin(%1) \
	(YSI_g_sClasses[(%1)][E_CLASS_FLAGS] & e_CLASS_FLAGS_SKIN)

/*----------------------------------------------------------------------------*-
Function:
	Class_Class
Params:
	-
Return:
	-
Notes:
	Creates three real player classes so you can scroll correctly with the
	direction being detected.
-*----------------------------------------------------------------------------*/

Class_Class()
{
	YSI_g_sLeft = AddPlayerClass(0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
	YSI_g_sMiddle = AddPlayerClass(0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
	YSI_g_sRight = AddPlayerClass(0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_OnPlayerRequestClass
Params:
	playerid - Player who requested a class.
	class - Class they requested.
Return:
	-
Notes:
	The input is one of the three real classes used to detect selected
	direction of alteration.  Scans for a class the player is allowed to use
	and hot swaps it out.  Uses SetPlayerSkin AND SetSpawnInfo to combat bugs
	with calling this from OnPlayerRequestSpawn (e.g. the example team script).
	Calls OnPlayerRequestClassEx with the current internal class not the real
	one.
-*----------------------------------------------------------------------------*/

Class_OnPlayerRequestClass(playerid, class)
{
	new
		playerclass = YSI_g_sPlayerClass[playerid],
		dir = 1;
	switch (playerclass & 0xF0000000)
	{
		case CLASS_RIGHT:
			if (class == YSI_g_sMiddle) dir = -1;
		case CLASS_MIDDLE:
			if (class == YSI_g_sLeft) dir = -1;
		default:
			if (class == YSI_g_sRight) dir = -1;
	}
	playerclass &= 0x0FFFFFFF;
	if (!YSI_g_sClassCount)
	{
		SetSpawnInfo(playerid, NO_TEAM, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
		SetPlayerSkin(playerid, 0);
		printf("*** Internal Error! No YSI classes found");
	}
	else
	{
		new
			old = playerclass;
		do
		{
			playerclass += dir;
			if (playerclass < 0) playerclass = YSI_g_sClassCount - 1;
			if (playerclass >= YSI_g_sClassCount) playerclass = 0;
		}
		while (playerclass != old && (!Class_Enabled(playerclass) || !Bit_Get(YSI_g_sClasses[playerclass][E_CLASS_PLAYERS], playerid)));
		SetSpawnInfo(playerid, NO_TEAM, Class_Skin(playerclass), 0.0, 0.0, 0.0, 0.0, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][0] & 0xFF, 1, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][1] & 0xFF, 1, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][2] & 0xFF, 1);
		SetPlayerSkin(playerid, Class_Skin(playerclass));
		YSI_g_sPlayerClass[playerid] = playerclass;
		if (class == YSI_g_sMiddle) YSI_g_sPlayerClass[playerid] |= CLASS_MIDDLE;
		if (class == YSI_g_sLeft) YSI_g_sPlayerClass[playerid] |= CLASS_LEFT;
		if (class == YSI_g_sRight) YSI_g_sPlayerClass[playerid] |= CLASS_RIGHT;
	}
	return CallRemoteFunction("OnPlayerRequestClassEx", "ii", playerid, playerclass);
}

/*----------------------------------------------------------------------------*-
Function:
	Class_OnPlayerConnect
Params:
	playerid - Player who joined.
Return:
	-
Notes:
	Resets the player's current class.
-*----------------------------------------------------------------------------*/

Class_OnPlayerConnect(playerid)
{
	YSI_g_sPlayerClass[playerid] = 0;
	YSI_g_sPlayerGroup[playerid] = -1;
	return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_OnPlayerRequestSpawn
Params:
	playerid - Player who selected a spawn.
Return:
	-
Notes:
	Has inbuilt protection for a bug where selections aren't correctly
	debounced so you can press shift twice at once which can mess up some
	scripts (e.g. the example team selection script).  Calls
	OnPlayerRequestSpawnEx with an additional class parameter.
-*----------------------------------------------------------------------------*/

Class_OnPlayerRequestSpawn(playerid)
{
	if (YSI_g_sClassCount)
	{
		static
			sLastRefuse[MAX_PLAYERS];
		new
			playerclass = YSI_g_sPlayerClass[playerid] & 0x0FFFFFFF;
		if (!Class_Enabled(playerclass) || !Bit_Get(YSI_g_sClasses[playerclass][E_CLASS_PLAYERS], playerid)) return 0;
		new
			Float:x = Class_X(playerclass),
			Float:y = Class_Y(playerclass),
			Float:z = Class_Z(playerclass),
			Float:a = Class_A(playerclass),
			time = GetTickCount();
		SetSpawnInfo(playerid, NO_TEAM, Class_Skin(playerclass), x, y, z, a, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][0] & 0xFF, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][0] >> 16, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][1] & 0xFF, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][1] >> 16, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][2] & 0xFF, YSI_g_sClasses[playerclass][E_CLASS_WEAPONS][2] >> 16);
		new
			ret;
		if ((time - sLastRefuse[playerid]) >= 1000) ret = CallRemoteFunction("OnPlayerRequestSpawnEx", "ii", playerid, playerclass);
		if (!ret)
		{
			sLastRefuse[playerid] = time;
		}
		else
		{
			new
				newgroup = YSI_g_sClasses[playerclass][E_CLASS_GROUP],
				oldgroup = YSI_g_sPlayerGroup[playerid];
			if (oldgroup != newgroup)
			{
				if (oldgroup != -1) Group_RemovePlayer(oldgroup, playerid);
				if (newgroup != -1) Group_AddPlayer(newgroup, playerid);
				YSI_g_sPlayerGroup[playerid] = newgroup;
			}
		}
		return ret;
	}
	else return 1;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_Add
Params:
	skin - Skin of the class.
	Float:x - X spawn location.
	Float:y - Y spawn location.
	Float:z - Z spawn location.
	Float:a - A spawn location.
	weapon1 - First weapon to give the class.
	ammo1 - Amount of ammo for first weapon.
	weapon2 - Second weapon to give the class.
	ammo2 - Amount of ammo for second weapon.
	weapon3 - Third weapon to give the class.
	ammo3 - Amount of ammo for third weapon.
Return:
	-
Notes:
	Pretty much AddPlayerClass but allows greater control over the classes.
-*----------------------------------------------------------------------------*/

stock Class_Add(skin, Float:x, Float:y, Float:z, Float:a, weapon1, ammo1, weapon2, ammo2, weapon3, ammo3)
{
	new
		i;
	while (i < MAX_CLASSES)
	{
		if (!Class_IsActive(i)) break;
		i++;
	}
	if (i == MAX_CLASSES) return -1;
	YSI_g_sClasses[i][E_CLASS_FLAGS] = 	e_CLASS_FLAGS_ACTIVE | e_CLASS_FLAGS:skin | e_CLASS_FLAGS_ENABLED;
	YSI_g_sClasses[i][E_CLASS_XY] = (floatround(x * 10.0) << 16) | (floatround(y * 10.0) & 0xFFFF);
	YSI_g_sClasses[i][E_CLASS_ZA] = (floatround(z * 10.0) << 16) | (floatround(a * 10.0) & 0xFFFF);
	YSI_g_sClasses[i][E_CLASS_WEAPONS][0] = weapon1 | (ammo1 << 8);
	YSI_g_sClasses[i][E_CLASS_WEAPONS][1] = weapon2 | (ammo2 << 8);
	YSI_g_sClasses[i][E_CLASS_WEAPONS][2] = weapon3 | (ammo3 << 8);
	YSI_g_sClasses[i][E_CLASS_GROUP] = -1;
	YSI_g_sClassCount++;
	return i;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_AddForGroup
Params:
	group - Group to allow to use the class.
	skin - Skin of the class.
	Float:x - X spawn location.
	Float:y - Y spawn location.
	Float:z - Z spawn location.
	Float:a - A spawn location.
	weapon1 - First weapon to give the class.
	ammo1 - Amount of ammo for first weapon.
	weapon2 - Second weapon to give the class.
	ammo2 - Amount of ammo for second weapon.
	weapon3 - Third weapon to give the class.
	ammo3 - Amount of ammo for third weapon.
Return:
	-
Notes:
	Adds a class only people in the specified group can use.
-*----------------------------------------------------------------------------*/

stock Class_AddForGroup(group, skin, Float:x, Float:y, Float:z, Float:a, weapon1, ammo1, weapon2, ammo2, weapon3, ammo3)
{
	new
		classid = Class_Add(skin, x, y, z, a, weapon1, ammo1, weapon2, ammo2, weapon3, ammo3);
	if (classid == -1) return -1;
	Group_SetDefaultClass(classid, 0);
	Group_SetClass(group, classid, 1);
	return classid;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_AddWithGroupSet
Params:
	group - Group to make players who use this group.
	skin - Skin of the class.
	Float:x - X spawn location.
	Float:y - Y spawn location.
	Float:z - Z spawn location.
	Float:a - A spawn location.
	weapon1 - First weapon to give the class.
	ammo1 - Amount of ammo for first weapon.
	weapon2 - Second weapon to give the class.
	ammo2 - Amount of ammo for second weapon.
	weapon3 - Third weapon to give the class.
	ammo3 - Amount of ammo for third weapon.
Return:
	-
Notes:
	Adds a class which puts you in the specified group when selected.
-*----------------------------------------------------------------------------*/

stock Class_AddWithGroupSet(group, skin, Float:x, Float:y, Float:z, Float:a, weapon1, ammo1, weapon2, ammo2, weapon3, ammo3)
{
	new
		classid = Class_Add(skin, x, y, z, a, weapon1, ammo1, weapon2, ammo2, weapon3, ammo3);
	if (classid == -1) return -1;
	YSI_g_sClasses[classid][E_CLASS_GROUP] = group;
	return classid;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_Enable
Params:
	classid - Class to re-enable.
Return:
	-
Notes:
	Reallow use of this class.
-*----------------------------------------------------------------------------*/

stock Class_Enable(classid)
{
	if (Class_IsValid(classid)) YSI_g_sClasses[classid][E_CLASS_FLAGS] |= e_CLASS_FLAGS_ENABLED;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_Disable
Params:
	classid - Class to disable.
Return:
	-
Notes:
	Stop people being able to use this class.
-*----------------------------------------------------------------------------*/

stock Class_Disable(classid)
{
	if (Class_IsValid(classid)) YSI_g_sClasses[classid][E_CLASS_FLAGS] &= ~e_CLASS_FLAGS_ENABLED;
}

/*----------------------------------------------------------------------------*-
Function:
	Class_SetPlayer
Params:
	classid - Class to set permissions for.
	playerid - Player to set for.
	set - Whether or not they can use this class.
Return:
	-
Notes:
	-
-*----------------------------------------------------------------------------*/

stock Class_SetPlayer(classid, playerid, set)
{
	if (Class_IsValid(classid) && playerid >= 0 && playerid < MAX_PLAYERS) Bit_Set(YSI_g_sClasses[classid][E_CLASS_PLAYERS], playerid, set);
}
