// Developers notes
/* # Coding guideline
* Add +1 at the end of array declarations to visually account for null bit.
* Always log first.
* Always notify discord last.
* Always wait for other queries to finish before initiating one: "sql_wait(sqlHandle);	// Wait for other queries to finish"
* Delete GVar strings as soon as possible, but at least before the player quits or the gamemode exits, as per: https://forum.sa-mp.com/showthread.php?t=151076
*/

/* # Style guide
* GLOBAL_CONSTANT
* local_variable
* someFunction
* SomeClass
* playerid  // Variable storing in-game player ID
* player_id // Variable storing database player ID
*/

/* # Adding a discord bot to your guild.
https://discordapp.com/oauth2/authorize?client_id=%CLIENT_ID%&scope=bot&permissions=3072
Where %CLEINT_ID% is the client id.
*/

/* # To do:
* Add geoiplib, to make connect messages fancier, and add a locate command.
* Show underscores as spaces and convert spaces in changename input to undescores before regex chack.
* Vip limit on characters.
* See why sql_insert_id wont work.
* Add all ADMIN_ECHO_CHANNEL messages to admin chat. (not the real admin chat)
* Check if timestamps work (implemented on temp ban), before implementing it more.
* Normalise database column names.
* Some SMALLINT fields are using SQL_INTERGER_LEGNTH for size and could be smaller.
*/


/// Global definitions
// Colours
/*
#define COLOR_GREEN 0x33AA33AA
#define COLOR_YELLOW 0xFFFF00AA
#define COLOR_WHITE 0xFFFFFFAA
#define COLOR_BLUE 0x0000BBAA
#define COLOR_LIGHTBLUE 0x33CCFFAA
#define COLOR_ORANGE 0xFF9900AA
#define COLOR_RED 0xAA3333AA
#define COLOR_LIME 0x10F441AA
#define COLOR_NAVY 0x000080AA
#define COLOR_AQUA 0xF0F8FFAA
#define COLOR_CRIMSON 0xDC143CAA
#define COLOR_BISQUE 0xFFE4C4AA
#define COLOR_BLACK 0x000000AA
#define COLOR_CHARTREUSE 0x7FFF00AA
#define COLOR_BROWN 0XA52A2AAA
#define COLOR_CORAL 0xFF7F50AA
#define COLOR_GOLD 0xB8860BAA
#define COLOR_GREENYELLOW 0xADFF2FAA
#define COLOR_INDIGO 0x4B00B0AA
#define COLOR_IVORY 0xFFFF82AA
#define COLOR_LAWNGREEN 0x7CFC00AA
#define COLOR_SEAGREEN 0x20B2AAAA
#define COLOR_SEAGREEN 0x2E8B57AA
#define COLOR_LIMEGREEN 0x32CD32AA //<--- Dark lime
#define COLOR_MIDNIGHTBLUE 0X191970AA
#define COLOR_MAROON 0x800000AA
#define COLOR_OLIVE 0x808000AA
#define COLOR_ORANGERED 0xFF4500AA
#define COLOR_PINK 0xFFC0CBAA // - Light light pink
#define COLOR_SPRINGGREEN 0x00FF7FAA
#define COLOR_TOMATO 0xFF6347AA // - Tomato >:/ sounds wrong lol... well... :P
#define COLOR_YELLOWGREEN 0x9ACD32AA //- like military green
#define COLOR_MEDIUMAQUA 0x83BFBFAA
#define COLOR_MEDIUMMAGENTA 0x8B008BAA // dark magenta ^^
*/
#define COLOR_ACTIVEBORDER 0xB4B4B4FF
#define COLOR_ACTIVECAPTION 0x99B4D1FF
#define COLOR_ACTIVECAPTIONTEXT 0x000000FF
#define COLOR_ALICEBLUE 0xF0F8FFFF
#define COLOR_ANTIQUEWHITE 0xFAEBD7FF
#define COLOR_APPWORKSPACE 0xABABABFF
#define COLOR_AQUA 0x00FFFFFF
#define COLOR_AQUAMARINE 0x7FFFD4FF
#define COLOR_AZURE 0xF0FFFFFF
#define COLOR_BEIGE 0xF5F5DCFF
#define COLOR_BISQUE 0xFFE4C4FF
#define COLOR_BLACK 0x000000FF
#define COLOR_BLANCHEDALMOND 0xFFEBCDFF
#define COLOR_BLUE 0x0000FFFF
#define COLOR_BLUEVIOLET 0x8A2BE2FF
#define COLOR_BROWN 0xA52A2AFF
#define COLOR_BURLYWOOD 0xDEB887FF
#define COLOR_BUTTONFACE 0xF0F0F0FF
#define COLOR_BUTTONHIGHLIGHT 0xFFFFFFFF
#define COLOR_BUTTONSHADOW 0xA0A0A0FF
#define COLOR_CADETBLUE 0x5F9EA0FF
#define COLOR_CHARTREUSE 0x7FFF00FF
#define COLOR_CHOCOLATE 0xD2691EFF
#define COLOR_CONTROL 0xF0F0F0FF
#define COLOR_CONTROLDARK 0xA0A0A0FF
#define COLOR_CONTROLDARKDARK 0x696969FF
#define COLOR_CONTROLLIGHT 0xE3E3E3FF
#define COLOR_CONTROLLIGHTLIGHT 0xFFFFFFFF
#define COLOR_CONTROLTEXT 0x000000FF
#define COLOR_CORAL 0xFF7F50FF
#define COLOR_CORNFLOWERBLUE 0x6495EDFF
#define COLOR_CORNSILK 0xFFF8DCFF
#define COLOR_CRIMSON 0xDC143CFF
#define COLOR_CYAN 0x00FFFFFF
#define COLOR_DARKBLUE 0x00008BFF
#define COLOR_DARKCYAN 0x008B8BFF
#define COLOR_DARKGOLDENROD 0xB8860BFF
#define COLOR_DARKGRAY 0xA9A9A9FF
#define COLOR_DARKGREEN 0x006400FF
#define COLOR_DARKKHAKI 0xBDB76BFF
#define COLOR_DARKMAGENTA 0x8B008BFF
#define COLOR_DARKOLIVEGREEN 0x556B2FFF
#define COLOR_DARKORANGE 0xFF8C00FF
#define COLOR_DARKORCHID 0x9932CCFF
#define COLOR_DARKRED 0x8B0000FF
#define COLOR_DARKSALMON 0xE9967AFF
#define COLOR_DARKSEAGREEN 0x8FBC8BFF
#define COLOR_DARKSLATEBLUE 0x483D8BFF
#define COLOR_DARKSLATEGRAY 0x2F4F4FFF
#define COLOR_DARKTURQUOISE 0x00CED1FF
#define COLOR_DARKVIOLET 0x9400D3FF
#define COLOR_DEEPPINK 0xFF1493FF
#define COLOR_DEEPSKYBLUE 0x00BFFFFF
#define COLOR_DEFAULT_CHAT 0xFFFFFFFF
#define COLOR_DESKTOP 0x000000FF
#define COLOR_DIMGRAY 0x696969FF
#define COLOR_DODGERBLUE 0x1E90FFFF
#define COLOR_FIREBRICK 0xB22222FF
#define COLOR_FLBLUE 0x6495EDAA
#define COLOR_FLORALWHITE 0xFFFAF0FF
#define COLOR_FORESTGREEN 0x228B22FF
#define COLOR_GAINSBORO 0xDCDCDCFF
#define COLOR_GHOSTWHITE 0xF8F8FFFF
#define COLOR_GOLD 0xFFD700FF
#define COLOR_GOLDENROD 0xDAA520FF
#define COLOR_GRADIENTACTIVECAPTION 0xB9D1EAFF
#define COLOR_GRADIENTINACTIVECAPTION 0xD7E4F2FF
#define COLOR_GRAY 0x808080FF
#define COLOR_GRAYTEXT 0x808080FF
#define COLOR_GREEN 0x008000FF
#define COLOR_GREENYELLOW 0xADFF2FFF
#define COLOR_GREY 0xAFAFAFAA
#define COLOR_HIGHLIGHT 0x3399FFFF
#define COLOR_HIGHLIGHTTEXT 0xFFFFFFFF
#define COLOR_HONEYDEW 0xF0FFF0FF
#define COLOR_HOTPINK 0xFF69B4FF
#define COLOR_HOTTRACK 0x0066CCFF
#define COLOR_INACTIVEBORDER 0xF4F7FCFF
#define COLOR_INACTIVECAPTION 0xBFCDDBFF
#define COLOR_INACTIVECAPTIONTEXT 0x434E54FF
#define COLOR_INDIANRED 0xCD5C5CFF
#define COLOR_INDIGO 0x4B0082FF
#define COLOR_INFO 0xFFFFE1FF
#define COLOR_INFOTEXT 0x000000FF
#define COLOR_IVORY 0xFFFFF0FF
#define COLOR_KHAKI 0xF0E68CFF
#define COLOR_LAVENDER 0xE6E6FAFF
#define COLOR_LAVENDERBLUSH 0xFFF0F5FF
#define COLOR_LAWNGREEN 0x7CFC00FF
#define COLOR_LEMONCHIFFON 0xFFFACDFF
#define COLOR_LIGHTBLUE 0xADD8E6FF
#define COLOR_LIGHTCORAL 0xF08080FF
#define COLOR_LIGHTCYAN 0xE0FFFFFF
#define COLOR_LIGHTGOLDENRODYELLOW 0xFAFAD2FF
#define COLOR_LIGHTGRAY 0xD3D3D3FF
#define COLOR_LIGHTGREEN 0x90EE90FF
#define COLOR_LIGHTPINK 0xFFB6C1FF
#define COLOR_LIGHTSALMON 0xFFA07AFF
#define COLOR_LIGHTSEAGREEN 0x20B2AAFF
#define COLOR_LIGHTSKYBLUE 0x87CEFAFF
#define COLOR_LIGHTSLATEGRAY 0x778899FF
#define COLOR_LIGHTSTEELBLUE 0xB0C4DEFF
#define COLOR_LIGHTYELLOW 0xFFFFE0FF
#define COLOR_LIME 0x00FF00FF
#define COLOR_LIMEGREEN 0x32CD32FF
#define COLOR_LINEN 0xFAF0E6FF
#define COLOR_MAGENTA 0xFF00FFFF
#define COLOR_MAROON 0x800000FF
#define COLOR_MEDIUMAQUAMARINE 0x66CDAAFF
#define COLOR_MEDIUMBLUE 0x0000CDFF
#define COLOR_MEDIUMORCHID 0xBA55D3FF
#define COLOR_MEDIUMPURPLE 0x9370DBFF
#define COLOR_MEDIUMSEAGREEN 0x3CB371FF
#define COLOR_MEDIUMSLATEBLUE 0x7B68EEFF
#define COLOR_MEDIUMSPRINGGREEN 0x00FA9AFF
#define COLOR_MEDIUMTURQUOISE 0x48D1CCFF
#define COLOR_MEDIUMVIOLETRED 0xC71585FF
#define COLOR_MENU 0xF0F0F0FF
#define COLOR_MENUBAR 0xF0F0F0FF
#define COLOR_MENUHIGHLIGHT 0x3399FFFF
#define COLOR_MENUTEXT 0x000000FF
#define COLOR_MIDNIGHTBLUE 0x191970FF
#define COLOR_MINTCREAM 0xF5FFFAFF
#define COLOR_MISTYROSE 0xFFE4E1FF
#define COLOR_MOCCASIN 0xFFE4B5FF
#define COLOR_NAVAJOWHITE 0xFFDEADFF
#define COLOR_NAVY 0x000080FF
#define COLOR_OLDLACE 0xFDF5E6FF
#define COLOR_OLIVE 0x808000FF
#define COLOR_OLIVEDRAB 0x6B8E23FF
#define COLOR_ORANGE 0xFFA500FF
#define COLOR_ORANGERED 0xFF4500FF
#define COLOR_ORCHID 0xDA70D6FF
#define COLOR_PALEGOLDENROD 0xEEE8AAFF
#define COLOR_PALEGREEN 0x98FB98FF
#define COLOR_PALETURQUOISE 0xAFEEEEFF
#define COLOR_PALEVIOLETRED 0xDB7093FF
#define COLOR_PAPAYAWHIP 0xFFEFD5FF
#define COLOR_PEACHPUFF 0xFFDAB9FF
#define COLOR_PERU 0xCD853FFF
#define COLOR_PINK 0xFFC0CBFF
#define COLOR_PLUM 0xDDA0DDFF
#define COLOR_POWDERBLUE 0xB0E0E6FF
#define COLOR_PURPLE 0x800080FF
#define COLOR_RED 0xFF0000FF
#define COLOR_ROSYBROWN 0xBC8F8FFF
#define COLOR_ROYALBLUE 0x4169E1FF
#define COLOR_SADDLEBROWN 0x8B4513FF
#define COLOR_SALMON 0xFA8072FF
#define COLOR_SANDYBROWN 0xF4A460FF
#define COLOR_SCROLLBAR 0xC8C8C8FF
#define COLOR_SEAGREEN 0x2E8B57FF
#define COLOR_SEASHELL 0xFFF5EEFF
#define COLOR_SIENNA 0xA0522DFF
#define COLOR_SILVER 0xC0C0C0FF
#define COLOR_SKYBLUE 0x87CEEBFF
#define COLOR_SLATEBLUE 0x6A5ACDFF
#define COLOR_SLATEGRAY 0x708090FF
#define COLOR_SNOW 0xFFFAFAFF
#define COLOR_SPRINGGREEN 0x00FF7FFF
#define COLOR_STEELBLUE 0x4682B4FF
#define COLOR_TAN 0xD2B48CFF
#define COLOR_TEAL 0x008080FF
#define COLOR_THISTLE 0xD8BFD8FF
#define COLOR_TOMATO 0xFF6347FF
#define COLOR_TRANSPARENT 0xFFFFFF00
#define COLOR_TURQUOISE 0x40E0D0FF
#define COLOR_VIOLET 0xEE82EEFF
#define COLOR_WHEAT 0xF5DEB3FF
#define COLOR_WHITE 0xFFFFFFFF
#define COLOR_WHITESMOKE 0xF5F5F5FF
#define COLOR_WINDOW 0xFFFFFFFF
#define COLOR_WINDOWFRAME 0x646464FF
#define COLOR_WINDOWTEXT 0x000000FF
#define COLOR_YELLOW 0xFFFF00FF
#define COLOR_YELLOWGREEN 0x9ACD32FF
#define COLOR_STEALTH_ORANGE 0xFF880000
#define COLOR_STEALTH_OLIVE 0x66660000
#define COLOR_STEALTH_GREEN 0x33DD1100
#define COLOR_STEALTH_PINK 0xFF22EE00
#define COLOR_STEALTH_BLUE 0x0077BB00

// Color groups
#define COLOR_COMMAND_OUTPUT 0xFFFFFFFF 			// White
#define COLOR_NOTICE 0xAFAFAFAA 					// Grey
#define COLOR_WARNING 0xFFA500FF        			// Orange? (Looks more yellow to me)
#define COLOR_ERROR 0xFF0000FF        				// Red
#define COLOR_GLOBAL_CHAT 0xFFFFFFFF        		// White
#define COLOR_PM_CHAT 0xFFFF00FF        			// Yellow
#define COLOR_VIP_CHAT 0x800080FF			  		// Purple
#define COLOR_CREW_CHAT 0xFF9900AA					// Orange
#define COLOR_ADMIN_CHAT 0xB8860BAA					// Gold
#define COLOR_POLICE 0x00008BFF                     //
#define COLOR_MEDIC 0xFFC0CBAA                      // Light pink
#define COLOR_FIRE 0xAA3333AA           			// Red
#define COLOR_SHERIFF 0xF5F5DCFF                    // Beige
#define COLOR_FBI 0x000080AA                        // Navy
#define COLOR_DMV 0xF0F8FFAA                        // Aqua
#define COLOR_NEWS 0x808000FF                       // Olive

#define EMBED_COLOR_GREY "{838383}"
#define EMBED_COLOR_RED "{FF0000}"
#define EMBED_COLOR_WHITE "{FFFFFF}"

// SQL datatypes
#define MAX_SQL_INTEGER 10
#define MAX_SQL_TIMESTAMP 19	// 1999-01-08 04:05:06 [5 + 4 + 2 + 2 +2 + 2 + 2]
#define MAX_SQL_FLOAT 16
#define MAX_SQL_HASH 128
#define MAX_SQL_IP 45
#define MAX_SQL_REASON 121

// Log levels
#define LOGLEVEL_CHAT -2
#define LOGLEVEL_COMMAND -1
#define LOGLEVEL_DEBUG 0
#define LOGLEVEL_INFO 1
#define LOGLEVEL_NOTICE 2
#define LOGLEVEL_WARNING 3
#define LOGLEVEL_ERROR 4
#define LOGLEVEL_CRITICAL 5
#define LOGLEVEL_PANIC 6

// Userlevels
enum{   // Noted values as comments, for database reference.
	UNREGISTERED_PLAYER,		// 0
	REGISTERED_PLAYER,			// 1
	REGULAR_PLAYER,				// 2    Spent many hours in and around the comunity.
	VIP_PLAYER,             	// 3    Donated.
	MODERATOR_CREW,				// 4
	VETERAN_CREW,				// 5    Inactive admins and management that are allowed to keep their rights.
	ADMIN_CREW,					// 6
	UNDERCOVER_ADMIN_CREW,  	// 7
	MANAGEMENT_CREW,			// 8
	UNDRECOVER_MANAGEMENT_CREW, // 9
	FOUNDER_PLAYER,				// 10
	UNDERCOVER_FOUNDER_PLAYER,	// 11
}

// Discord channels
enum{
	ECHO_CHANNEL,
	MAIN_CHANNEL,
	ADMIN_ECHO_CHANNEL,
	ADMIN_CHANNEL,
	MANAGEMENT_CHANNEL,
}

// Chats
enum{
    CHAT_LOCAL, // Has to be 0, else a gVar would have to be created OnJoin, this conserves memory when no chatmode is set (which will be the case most likely)
	CHAT_WHISPER,
	CHAT_LOW,
	CHAT_ACTION,
	CHAT_SHOUT,
	CHAT_OC,
	CHAT_GLOBAL,
	CHAT_CALL,
	CHAT_SMS,
	CHAT_RADIO,
	CHAT_PM,
	CHAT_GANG,
	CHAT_GANG_OC,
	CHAT_FACTION,
	CHAT_FACTION_OC,
	CHAT_VIP,
	CHAT_CREW,
	CHAT_ADMIN,
	CHAT_MANAGEMENT,
	CHAT_UNDERCOVER,
}

// Dialogs
enum{
	DIALOG_CHANGENAME,
	DIALOG_REGISTER,
	DIALOG_ACCOUNT_CREATED,
	DIALOG_LOGIN,
	DIALOG_CHANGE_USERNAME,
	DIALOG_CHARACTERS,
	DIALOG_LOGIN_FAILED,
	DIALOG_DELETE_CHARACTER,
	DIALOG_GOTO,
	DIALOG_GOTO_INTERIOR,
	DIALOG_GOTO_PLAYER,
	DIALOG_GOTO_247,
	DIALOG_GOTO_AVIATION,
	DIALOG_GOTO_AMMUNATION,
	DIALOG_GOTO_BURGLARY,
    DIALOG_GOTO_MISSION,
    DIALOG_GOTO_MISSIONEXT,
    DIALOG_GOTO_MISSIONHOUSE,
    DIALOG_GOTO_MODDING,
	DIALOG_GOTO_POLICE,
	DIALOG_GOTO_HOUSE,
	DIALOG_GOTO_SHOP,
	DIALOG_GOTO_STADIA,
	DIALOG_CREATE_PICKUP,
	DIALOG_CREATE_FACTION_PICKUP,
	DIALOG_DUTY_PD,
	DIALOG_POLICE_SKIN,
	DIALOG_DUTY_MD,
	DIALOG_MEDIC_SKIN,
	DIALOG_DUTY_FD,
	DIALOG_FIRE_SKIN,
	DIALOG_DUTY_SD,
	DIALOG_SHERIFF_SKIN,
	DIALOG_DUTY_FBI,
	DIALOG_FBI_SKIN,
	DIALOG_DUTY_DMV,
	DIALOG_DMV_SKIN,
	DIALOG_DUTY_NEWS,
	DIALOG_NEWS_SKIN,
}

// Pickups
enum{
	PICKUP_FACTION_PD,
	PICKUP_FACTION_MD,
	PICKUP_FACTION_FD,
	PICKUP_FACTION_SD,
	PICKUP_FACTION_FBI,
	PICKUP_FACTION_DMV,
	PICKUP_FACTION_NEWS,
	PICKUP_PORTAL,  // MUST be last as it's not in the pickup create dialog
}

// Location categories
enum{
	GOTO_CATEGORY_INTERIOR,
}
enum{
	INTERIOR_CATEGORY_247,
	INTERIOR_CATEGORY_AVIATION,
	INTERIOR_CATEGORY_AMMUNATION,
	INTERIOR_CATEGORY_BUGRLARY,
	INTERIOR_CATEGORY_MISSION,
	INTERIOR_CATEGORY_MISSIONEXT,
	INTERIOR_CATEGORY_MISSIONHOUSE,
	INTERIOR_CATEGORY_MODSHOP,
	INTERIOR_CATEGORY_POLICE,
	INTERIOR_CATEGORY_HOUSES,
	INTERIOR_CATEGORY_SHOPS,
	INTERIOR_CATEGORY_STADIA,
}
enum{INTERIOR_247_LSHAPED, INTERIOR_247_OBLONG, INTERIOR_247_MEDIUM, INTERIOR_247_MEDIUM_NOEXIT, INTERIOR_247_LONG, INTERIOR_247_SQAURE}
enum{
    INTERIOR_AVIATION_TICKET,
    INTERIOR_AVIATION_BAGGAGE,
    INTERIOR_AVIATION_SHAMAL,
    INTERIOR_AVIATION_ANDROMADA,
    INTERIOR_AVIATION_LSAIRPORT
}
enum{INTERIOR_AMMU_OCEAN, INTERIOR_AMMU_PALOMINO, INTERIOR_AMMU_ANGEL, INTERIOR_AMMU_QUEBRADOS, INTERIOR_AMMU_2STORIES, INTERIOR_AMMU_BOOTH, INTERIOR_AMMU_RANGE}
enum{
	INTERIOR_BURGRLARY_LARGE,
	INTERIOR_BURGRLARY_MEDIUM,
	INTERIOR_BURGRLARY_SMALL,
	INTERIOR_BURGRLARY_VERYLARGE,
	INTERIOR_BURGRLARY_5,
	INTERIOR_BURGRLARY_6,
	INTERIOR_BURGRLARY_NOBATH,
}
enum{
	INTERIOR_MISSION_ATRIUM,
	INTERIOR_MISSION_BIGSMOKE,
	INTERIOR_MISSION_JEFFERSON,
	INTERIOR_MISSION_JIZZY,
	INTERIOR_MISSION_RC,
	INTERIOR_MISSION_WUZI,
}
enum{
    INTERIOR_MISSIONEXT_GAS,
	INTERIOR_MISSIONEXT_LIBERTY,
	INTERIOR_MISSIONEXT_SFGARAGE,
}
enum{
   	INTERIOR_MISSIONHOUSE_DESIRE,
	INTERIOR_MISSIONHOUSE_COLONEL,
	INTERIOR_MISSIONHOUSE_RYDER,
	INTERIOR_MISSIONHOUSE_SWEET,
	INTERIOR_MISSIONHOUSE_CRACK,
}
enum{INTERIOR_MOD_LOCO, INTERIOR_MOD_WHEEL, INTERIOR_MOD_TRANSFENDER}
enum{INTERIOR_PD_LV, INTERIOR_PD_LS, INTERIOR_PD_SF, INTERIOR_PD_BARBARA}
enum{
    INTERIOR_HOUSE_GOLDEN,
	INTERIOR_HOUSE_HASHBURY,
	INTERIOR_HOUSE_JOHNSON,
	INTERIOR_HOUSE_MADDDOGG,
	INTERIOR_HOUSE_RED,
	INTERIOR_HOUSE_VERDANT,
	INTERIOR_HOUSE_UNUSED,
}
enum{
    INTERIOR_SHOP_TATTOO,
	INTERIOR_SHOP_BURGER,
	INTERIOR_SHOP_PIZZA,
	INTERIOR_SHOP_CLUCKIN,
	INTERIOR_SHOP_CALIGULAS,
	INTERIOR_SHOP_CASINO,
	INTERIOR_SHOP_4DRAGONS,
	INTERIOR_SHOP_DONUTS,
	INTERIOR_SHOP_RC,
	INTERIOR_SHOP_PUMP,
}
enum{
	INTERIOR_STADIUM_BLOODBOWL,
	INTERIOR_STADIUM_KICKSTART,
	INTERIOR_STADIUM_8TRACK,
	INTERIOR_STADIUM_DIRTBIKE,
}

// Factions
enum{
    FACTION_NONE,
	FACTION_POLICE,
	FACTION_MEDIC,
	FACTION_FIRE,
	FACTION_SHERIFF,
	FACTION_FBI,
    FACTION_DMV,
    FACTION_NEWS,
}

// Jobs
enum{
	JOB_NONE,
	JOB_POLICE,
	JOB_MEDIC,
	JOB_FIRE,
	JOB_SHERIFF,
	JOB_FBI,
	JOB_DMV,
	JOB_NEWS,
}

// Environment settings	TODO: THESE SHOULD ALL BE READ FROM A CONFIG FILE!
static bool:scriptDebug = true;	// Debug setting
#define MODE_NAME "0.0a Build 9"
#define SERVER_NAME "Bone County RPG"
#define PG_HOST "127.0.0.1"
#define PG_ROLE "rpfw-dev"
#define PG_PASS "nJd&1k$0fs"
#define PG_DB "rpfw-dev"
#define PG_PORT 5432
#define DISCORD_HOME_GUILD_ID "666077037470941184"			// Emerald City Roleplay
#define DISCORD_ECHO_CHANNEL_ID "677855051166777344"		// #bcrp-echo
//#define DISCORD_ECHO_CHANNEL_ID "666078187079598080"		// #ecrp-echo
#define DISCORD_MAIN_CHANNEL_ID "677855315898793984"		// #bcrp
//#define DISCORD_MAIN_CHANNEL_ID "667396220402270228"		// #development
#define DISCORD_ADMIN_ECHO_CHANNEL_ID "672841892169383936"	// #admin-echo
#define DISCORD_ADMIN_CHANNEL_ID "667396026058932236"		// #admins
#define DISCORD_MANAGEMENT_CHANNEL_ID "666091376240361492"	// #management

// Game-mode limits
#define MAX_CHARACTERS_PER_USER 20  // Maximum of MAX_CHARACTERS_PER_USER_DIGITS digits (Due to SQL query string length)
#define MAX_CHARACTERS_PER_USER_DIGITS 3

// SQL plugin
new SQL:sqlHandle;

// discord-connector
//static DCC_Guild:homeGuild;			// Discord guild controlled by game community.
static DCC_Channel:echoChannel;			// Public echo channel.
static DCC_Channel:mainChannel;			// Channel for communirty notifications.
static DCC_Channel:adminEchoChannel;	// Admin echo channel.
static DCC_Channel:adminChannel;		// Channel for admin notifications.
static DCC_Channel:managementChannel;	// Channel for management notifications.

DiscordEcho(const message[], messageLevel){ // Write to echo channels.
	switch(messageLevel)	// Output facilities.
    {
        case ECHO_CHANNEL: {                                                                // Public echo message
			DiscordSendChannelMessage(echoChannel, message);
			DiscordSendChannelMessage(adminEchoChannel, message);   // Also send to admin echo channel, so admin can see everything in one channel without needing to constantly switch. Also handy as log on conflicts/complaints.
		}
		case MAIN_CHANNEL: {DiscordSendChannelMessage(mainChannel, message);}				// Public notification
        case ADMIN_ECHO_CHANNEL: {DiscordSendChannelMessage(adminEchoChannel, message);}	// Admin echo message
		case ADMIN_CHANNEL: {DiscordSendChannelMessage(adminChannel, message);}				// Admin notification
        case MANAGEMENT_CHANNEL: {DiscordSendChannelMessage(managementChannel, message);}	// Management notification
    }

	return 0;
}

// Natives
native WP_Hash(buffer[], len, const str[]);	// https://forum.sa-mp.com/showthread.php?t=570945
//native Float:loadavg();	// https://forum.sa-mp.com/showthread.php?t=260206	LINUX ONLY

// Includes
#include <a_samp>	// https://sa-mp.com
#include <sql>	// https://github.com/udan11/samp-plugin-sql	(Fastest player in town and only one supporting postgreSQL) | Examples: https://pastebin.com/67y2nq2n https://github.com/udan11/samp-plugin-sql/issues/10
#include <sscanf2>	// Newest version: https://github.com/maddinat0r/sscanf/releases | Better readme: https://github.com/Y-Less/sscanf
#include <strlib>	// https://github.com/oscar-broman/strlib
#include <Pawn.Regex>	// https://github.com/urShadow/Pawn.Regex
#include <Pawn.CMD>	// https://github.com/urShadow/Pawn.CMD	(Fastest player in town)
#include <gvar>	// https://github.com/samp-incognito/samp-gvar-plugin
#include <discord-connector>	// https://github.com/maddinat0r/samp-discord-connector	(Only player in town)
#include <streamer>	// https://github.com/samp-incognisto/samp-streamer-plugin


/// Middle-ware
// Logging
logger(const log_level, const message[]){	// Write to logging facility
	// Do not log commands or debug when script debugging is turned off.
	if(scriptDebug == false){   // Debug is off
		if(log_level == LOGLEVEL_COMMAND || log_level == LOGLEVEL_DEBUG){   // Command or debug message

			return 0;   // Stop and do not log
		}
	}
	
	// Messagelevel tag
	new human_readable_log_level[8 + 1];
	switch(log_level){	// Assign log level.
		case LOGLEVEL_CHAT: human_readable_log_level = "chat";
		case LOGLEVEL_COMMAND: human_readable_log_level = "command";
        case LOGLEVEL_DEBUG: human_readable_log_level = "debug";
        case LOGLEVEL_INFO: human_readable_log_level = "info";
        case LOGLEVEL_NOTICE: human_readable_log_level = "notice";
		case LOGLEVEL_WARNING: human_readable_log_level = "warning";
		case LOGLEVEL_ERROR: human_readable_log_level = "error";
		case LOGLEVEL_CRITICAL: human_readable_log_level = "critical";
		case LOGLEVEL_PANIC: human_readable_log_level = "panic";
    }
    
	printf("[%s] %s", human_readable_log_level, message);	// Print to STDOUT.

	return 0;
}

/*// SQL plugin (BROKEN, look at it again after I have more then a week of experience)
//forward sqlQuery(SQL:handle, query[]);
//public sqlQuery(SQL:handle, query[]){
sqlQuery(SQL:handle, query[]){
	//sql_wait(handle);	// Wait for all queries to finish.

	new Result:result = sql_query(handle, query);
	if(sql_error(result)){printf("SQL error");}	// Did not work during a test with a faulty statement.
	return result;
}*/

// discord-connector
DiscordSendChannelMessage(DCC_Channel:channel, const message[]){
	if(scriptDebug){    // Log middle-ware event in debugging mode only
		new channel_name[100 + 1];	// Default value from tutorial
		DCC_GetChannelName(channel, channel_name);
		new logMessage[26 + 100 + 2000 + 1];	// Discord max message length = 2000
		format(logMessage, sizeof(logMessage), "Send to discord channel %s: %s", channel_name, message);

		logger(LOGLEVEL_DEBUG, logMessage);	// Actually log the message
	}

	DCC_SendChannelMessage(channel, message);	// Call discord-connector	DISABLE THIS TO STOP ALL OUTGOING DISCORD MESSAGES
}


/// Game-mode
// Account functions
forward changeName(playerid, const name[]);
public changeName(playerid, const name[]){	// Check if name is valid and force change if needed.
	// Prevent regex error when comparing against null
	if(isempty(name)){  // No name entered
	    ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "You must enter a name.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", "");	// Force RP name.
	    
	    return 0;
	}
	
	// Check name
	new Regex:r = Regex_New("^[A-Z][a-z]{1,}([A-Z][a-z]{1,})?(_([A-Z][a-z]{1,}([A-Z][a-z]{1,})?|[A-Z]\\.(_[A-Z]\\.)?))?_[A-Z][a-z]{1,}([A-Z][a-z]{1,})?$");	// Regex name filter
	new isValidName = Regex_Check(name, r); // Validate name to filter
	Regex_Delete(r);
	if(!isValidName){	// Invalid role-play name
		ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Please pick a realistic name, separate first and last name with an underscore.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", "");	// Force RP name.
	}
	else {	// Valid role-play name
	    // Check for name in use
		/*new escaped_name[MAX_PLAYER_NAME + 1];  // TODO should be more, to account for escape characters.
	    sql_escape_string(sqlHandle, name, escaped_name, sizeof(escaped_name));	// Escape player name   BROKEN argument type mismatch on argument 2, but name is a sting...*/
	    new character_query[40 + MAX_PLAYER_NAME + 1], Result:character_result = sql_query(sqlHandle, character_query);
		format(character_query, sizeof(character_query), "SELECT id FROM character WHERE name = '%s'", name);
		
        if(sql_num_rows(character_result) > 0){  // Name taken
            ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Name already taken. Please pick an original name.", "Change", "");	// Force RP name.
        }
        else{   // Name free
            // Notify all players
			new message[11 + 4 + MAX_PLAYER_NAME + 1];

			format(message, sizeof(message), "* [%i] %s joined.", playerid, getCharacterName(playerid));
			SendClientMessageToAll(COLOR_NOTICE, message);	// Notify all players
			DiscordEcho(message, ECHO_CHANNEL);	// Notify discord public echo
			
			new client_connect_username[MAX_PLAYER_NAME + 1], admin_message[38 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
            GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);   // Get client connect name
		    if(GetPlayerState(playerid) == PLAYER_STATE_NONE){	// Not spawned: character creation (Creating a character without registering)
		        // Notify admins
	            
	            format(admin_message, sizeof(admin_message), "* [%i] %s temporary charcter, created by: %s", playerid, getCharacterName(playerid), client_connect_username);
				sendToAdmins(COLOR_NOTICE, admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);

	            // Do nothing let continue to spawn, after spawn character will be saved to database.
		    }
	    	else{   // Spawnedplayer: Creating permanent or renaming an existing permanent character
	    	    // Notify admins
	            format(admin_message, sizeof(admin_message), "* [%i] %s created by: %s", playerid, getCharacterName(playerid), client_connect_username);
				sendToAdmins(COLOR_NOTICE, admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
				
                // Send to skin selection
                ForceClassSelection(playerid);
                TogglePlayerSpectating(playerid, true);
			    TogglePlayerSpectating(playerid, false);
			}
	    
			// Change name  (No way yet to change name ingame)
			/*if(GetGVarInt("userlevel", playerid) > 1){	// Registered player.
				// Get username
				new username[MAX_PLAYER_NAME + 1], message[75 + MAX_PLAYER_NAME + 1];
				GetGVarString("username", username, sizeof(username), playerid);

				// Update or create character name in database
				new callback[1];
				new Result:result;
				new query[51 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
				format(query, sizeof(query), "UPDATE \"user\"(name) VALUES('%s') WHERE username == '%s'", name, username);
				sql_wait(sqlHandle);	// Wait for other queries to finish
				result = sql_query(sqlHandle, query, callback = "", "r");
				
				// Inform user
				format(message, sizeof(message), "SERVER: Your username remains unchanged, next time connect as: %s", client_connect_username);
				SendClientMessage(playerid, COLOR_WHITE, message);
			}*/

			SetPlayerName(playerid, name);	// Change name in-game
		}
	}
	
	return 0;
}

forward register(playerid);
public register(playerid){	// Register player in database
	if(GetPlayerState(playerid) == PLAYER_STATE_NONE){	// Not spawned
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "ERROR: You need to have spawned to register.");
	}
	else{	// Spawned player
		if(GetGVarInt("userlevel", playerid) != UNREGISTERED_PLAYER){	// Registered player.
			new message[35 + MAX_PLAYER_NAME + 1];
			format(message, sizeof(message), "ERROR: You are already registered"); //, %s.",
			SendClientMessage(playerid, COLOR_WHITE, message);
		}
		else{	// Unregistered player.
			ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password", "Please pick a strong and safe password, that you are able to remember.", "Continue", "Cancel");	// Password prompt
		}
	}
}

forward createCharacterRecord(playerid, id);
public createCharacterRecord(playerid, id){
	new character_query[94 + MAX_SQL_INTEGER + MAX_PLAYER_NAME + 3 + 1];
	format(character_query, sizeof(character_query), "INSERT INTO character(user_id, name, skin_id) VALUES(%i, '%s', %i)", id, getPlayerName(playerid), GetPlayerSkin(playerid));
	sql_query(sqlHandle, character_query);
}

forward authenticate(playerid);
public authenticate(playerid){
    ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Sign in", "Enter your password to log on", "Log in", "Cancel");	// Show password confirmation dialog
}

forward characterSelection(playerid);
public characterSelection(playerid){
	if(GetGVarInt("userlevel", playerid) < REGISTERED_PLAYER){  // Unregistered player (Or worse?)
	    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Register first with: /my account register");
	}
	else{   // Registered player
		// If spawned, save current character
		if(GetPlayerState(playerid) != PLAYER_STATE_NONE){
		    savePlayerState(playerid);
		}

		new id = getUserID(playerid);
		new character_query[75 + MAX_SQL_INTEGER + MAX_CHARACTERS_PER_USER_DIGITS + 1];
		format(character_query, sizeof(character_query), "SELECT id, name FROM character WHERE user_id = %i LIMIT %i", id, MAX_CHARACTERS_PER_USER);
		new Result:character_result = sql_query(sqlHandle, character_query);

		new character_string[(MAX_PLAYER_NAME * MAX_CHARACTERS_PER_USER) + 1], character_array[MAX_CHARACTERS_PER_USER + 1];
		for(new i = 0; i < sql_num_rows(character_result); i++){
			new name[MAX_PLAYER_NAME + 1];
			sql_get_field_assoc_ex(character_result, i, "name", name, sizeof(name));
			format(character_string, sizeof(character_string), "%s%s\n", character_string, name);
			character_array[i] = sql_get_field_assoc_int_ex(character_result, i, "id");
			printf("Character ID from array: %i", character_array[i]);
		}
		new character_array_string[MAX_CHARACTERS_PER_USER * MAX_SQL_INTEGER + 1];
		strfrombin(character_array_string, character_array);    // Convert array to string for use with GVar
		SetGVarString("character_array_string", character_array_string, playerid);

		ShowPlayerDialog(playerid, DIALOG_CHARACTERS, DIALOG_STYLE_LIST, "Characters", character_string, "Spawn", "New");
	}
}

forward savePlayerState(playerid);
public savePlayerState(playerid){
	new escaped_playername[MAX_PLAYER_NAME + 1], character_query[210 + MAX_SQL_INTEGER + 3 + 3 + 1 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_PLAYER_NAME + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];    // Should be longer to allow for escaped characters
	sql_escape_string(sqlHandle, getPlayerName(playerid), escaped_playername, sizeof(escaped_playername));	// Escape player name
	new Float:health, Float:armour, Float:x, Float:y, Float:z, Float:Angle;
	// Unrealiable TODO change to gvars
	GetPlayerHealth(playerid, health);
	GetPlayerArmour(playerid, armour);
	GetPlayerPos(playerid, x, y, z);
	GetPlayerFacingAngle(playerid, Angle);
	new world_id = GetPlayerVirtualWorld(playerid);
	new interior_id = GetPlayerInterior(playerid);
	
	format(character_query, sizeof(character_query), "UPDATE character SET (cash, health, armour, jailed, pos_x, pos_y, pos_z, rotation, world_id, interior_id) = (%i, '%f', '%f', %i, '%f', '%f', '%f', '%f', %i, %i) WHERE name = '%s'", GetPlayerMoney(playerid), health, armour, 0, x, y, z, Angle, world_id, interior_id, escaped_playername);
	sql_query(sqlHandle, character_query);
}

forward kickPlayerDelay(playerid);
public kickPlayerDelay(playerid){
    Kick(playerid);
}

forward kickPlayer(playerid, kickerid, const reason[], banned);
public kickPlayer(playerid, kickerid, const reason[], banned){	// Kick a player
    // Issuer of kick
    new kickername[MAX_PLAYER_NAME + 1], kicker_id[MAX_SQL_INTEGER + 1];
    if(kickerid == -1){ // Not kicked by in-game player
        kickername = "SERVER";
        kicker_id = "NULL";
	}
	else{   //  Kicked by in-game player
	    kickername = getPlayerName(playerid);
	    kicker_id = "%s", getUserID(kickerid);
	}
	strreplace(kickername, "_", " ");

	// Action
	new action[16 + MAX_SQL_INTEGER + 1];
	if(banned){
	    if(banned < 1){   // Permanent ban
			action = "banned permanently";
		}
		else{   // Temporary ban
	        format(action, sizeof(action), "banned for %i days", banned);
		}
    }
    else{
        action = "kicked";
	}

	// Notify all players
	new playername[MAX_PLAYER_NAME + 1], message[16 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 16 + MAX_SQL_REASON + 1], admin_message[23 + 4 + MAX_PLAYER_NAME + 16 + MAX_PLAYER_NAME + MAX_SQL_REASON + 1];	// Max samp chat message length 128 - 8 for "/kick ? ".
    GetPlayerName(playerid, playername, sizeof(playername));
	format(message, sizeof(message), "* [%i] %s %s, reason: %s", playerid, getCharacterName(playerid), action, reason);
	format(admin_message, sizeof(admin_message), "* [%i] %s kicked %s, reason: %s", kickerid, kickername, getCharacterName(playerid), action, reason);
	logger(LOGLEVEL_INFO, message);	// Log event
	SendClientMessageToAll(COLOR_NOTICE, message);
	DiscordEcho(message, ECHO_CHANNEL);
	sendToAdmins(COLOR_NOTICE, admin_message);
	DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);

	// Inform player (Redundant, but some people are blind or stupid)
	new player_message[30 + 6 + MAX_PLAYER_NAME + MAX_SQL_REASON + 1];	// Max samp chat message length 128 - 8 for "/kick ? ".
	format(player_message, sizeof(player_message), "SERVER: You have been %s, reason: %s", action, reason);
	SendClientMessage(playerid, COLOR_ERROR, player_message);	// Notify player
	if(banned){
	    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "You may appeal this ban via the forum or Discord.");
	}

	new ip_id = getIPID(playerid);
	// Crearte IP kick record
	new ip_query[61 + 4 + MAX_SQL_REASON + 4 + 1];
	format(ip_query, sizeof(ip_query), "INSERT INTO ip_kick(ip_id, reason, kicker_id) VALUES(%i, '%s', %s)", ip_id, reason, kicker_id);
	sql_query(sqlHandle, ip_query);

	// Get IP kick record
	new ip_id_query[64 + 4 + MAX_SQL_REASON + 4 + 1];
	format(ip_id_query, sizeof(ip_id_query), "SELECT id FROM ip_kick WHERE ip_id = %i ORDER BY id DESC LIMIT 1", ip_id);
	new Result:ip_id_result = sql_query(sqlHandle, ip_id_query);
	new ip_kick_id = sql_get_field_assoc_int(Result:ip_id_result, "id");

	// User kick record
	printf("Userlevel: %i", GetGVarInt("userlevel", playerid));
	if(GetGVarInt("userlevel", playerid) > UNREGISTERED_PLAYER){    // Not logged in to a user account
		new user_query[79 + 4 + MAX_SQL_REASON + 4 + 1];
		format(user_query, sizeof(user_query), "INSERT INTO user_kick(user_id, reason, kicker_id, ip_kick_id) VALUES(%i, '%s', %s, %i)", getUserID(playerid), reason, kicker_id, ip_kick_id);
		sql_query(sqlHandle, user_query);
	}

	SetTimerEx("kickPlayerDelay", 50, false, "i", playerid);	// Give the message 50 milliseconds to reach the player before kicking.
}

banExpiration(days);
banExpiration(days){
	if(days < 1){   // Permanent ban
	    new foo[MAX_SQL_TIMESTAMP + 1] = "0";
	    return foo;
	}
	else{   // Temporary ban
	    new year, month, day, hour, minute, second, expires[MAX_SQL_TIMESTAMP + 1];
		getdate(year, month, day);
		gettime(hour, minute, second);
		day = day + days;
		format(expires, sizeof(expires), "%i-%i-%i %i:%i:%i", year, month, day, hour, minute, second);
		
		return expires;
		//return year, month, day, hour, minute, second;
		//return 0;
	}
}

forward banPlayer(playerid, bannerid, const reason[], days);
public banPlayer(playerid, bannerid, const reason[], days){	// Ban & kick a player
	new ip_id = getIPID(playerid);
	// Create IP ban record
    new banner_id, ip_query[72 + MAX_SQL_INTEGER + MAX_SQL_TIMESTAMP + MAX_SQL_REASON + MAX_SQL_INTEGER + 1];
	if(bannerid < 0){   // Not banned by in-game player
	    format(ip_query, sizeof(ip_query), "INSERT INTO ip_ban(ip_id, expires, reason) VALUES(%i, '%s', '%s')", ip_id, banExpiration(days), reason);
	}
	else{   // Banned by in-game player
	    banner_id = getUserID(bannerid);
	    format(ip_query, sizeof(ip_query), "INSERT INTO ip_ban(ip_id, expires, reason, banner_id) VALUES(%i, '%s', '%s', %i)", ip_id, banExpiration(days), reason, banner_id);
	}
	sql_query(sqlHandle, ip_query);

	// Get IP ban record ID
	new ban_id_query[61 + MAX_PLAYER_NAME + 1];
	format(ban_id_query, sizeof(ban_id_query), "SELECT id FROM ip_ban WHERE ip_id = %i ORDER BY id DESC LIMIT 1", ip_id);
	new Result:ban_id_result = sql_query(sqlHandle, ban_id_query);
 	new ip_ban_id = sql_get_field_assoc_int(ban_id_result, "id");

	// User ban record (As banned players get only kicked, noneed to check for active ban)
	if(GetGVarInt("userlevel", playerid) > 1){  // Registered player
		new user_query[97 + MAX_SQL_INTEGER + MAX_SQL_TIMESTAMP + MAX_SQL_REASON + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
		format(user_query, sizeof(user_query), "INSERT INTO user_ban(user_id, expires, reason, banner_id, ip_ban_id) VALUES(%i, %s, %s, %i, %i) WHERE id = %i", ip_id, banExpiration(days), reason, banner_id, ip_ban_id);
		sql_query(sqlHandle, user_query);
	}

	// Kick player
	kickPlayer(playerid, bannerid, reason, days);
}


// Database functions
forward getIPID(playerid);
public getIPID(playerid){
    new player_ip[MAX_SQL_IP + 1], query[37 + MAX_SQL_IP + 1]; // Create varaibles
    GetPlayerIp(playerid, player_ip, sizeof(player_ip));    // Polulate player_ip variable
	format(query, sizeof(query), "SELECT id FROM ip WHERE address = '%s'", player_ip);   // Format query string
	new Result:result = sql_query(sqlHandle, query);    // Execute query
	
	return sql_get_field_assoc_int(result, "id");  // Get id from result
}

forward getUserID(playerid);
public getUserID(playerid){
	// SQL escape username
    new client_connect_username[MAX_PLAYER_NAME + 1], escaped_username[MAX_PLAYER_NAME + 1]; // TODO escaped_username should be longer to account for escape characters (Also increate database column size!)
	GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);   // Get client connect name
	sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username));	// Escape player name

	// Get user ID
	new userid_query[47 + MAX_PLAYER_NAME + 1];
	format(userid_query, sizeof(userid_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username);
	new Result:result = sql_query(sqlHandle, userid_query);

	return sql_get_field_assoc_int(result, "id");
}

// Player functions
forward deleteAllGVars(playerid);
public deleteAllGVars(playerid){    // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076
	DeleteGVar("client_connect_username", playerid);    // From OnPlayerConnect()
	DeleteGVar("userlevel", playerid);  				// From OnPlayerConnect()
	DeleteGVar("hash", playerid);       				// From DIALOG_REGISTER
	DeleteGVar("character_array_string", playerid);		// From OnDialogResponse()
	DeleteGVar("authentication_count", playerid);       // From DIALOG_LOGIN
	DeleteGVar("character_id", playerid);                // From DIALOG_CHARACTERS
	DeleteGVar("chatmode", playerid);                   // From cmd:my()
	DeleteGVar("disable_pickups", playerid);			// From OnPlayerPickUpDynamicPickup()
	DeleteGVar("goto_target", playerid);				// From cmd:my() & cmd:p()
	DeleteGVar("job", playerid);                        // From OnDialogResponse()
}

getPlayerName(playerid);
getPlayerName(playerid){
//	new playername[MAX_PLAYER_NAME + 1];
	new const playername[MAX_PLAYER_NAME + 1];
	GetPlayerName(playerid, playername, sizeof(playername));

//	new output[MAX_PLAYER_NAME + 1];
//	strfromliteral(output, playername);
	
	return playername;
}

forward teleportPlayer(playerid, Float:x, Float:y, Float:z, world, interior, Float:angle, const name[]);
public teleportPlayer(playerid, Float:x, Float:y, Float:z, world, interior, Float:angle, const name[]){
    SetPlayerPos(playerid, x, y, z);
	SetPlayerVirtualWorld(playerid, world);
	SetPlayerInterior(playerid, interior);
	SetPlayerFacingAngle(playerid, angle);
	
	// Inform admins
	new admin_message[21 + 4 + MAX_PLAYER_NAME + 5 + 4 + MAX_PLAYER_NAME + 1 + 1];  // 5 + 4 + MAX_PLAYER_NAME + 1 = name
	format(admin_message, sizeof(admin_message), "* [%i] %s teleported to: %s", playerid, getCharacterName(playerid), name);
	sendToAdmins(COLOR_NOTICE, admin_message);
	DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
}

forward setPlayerSkin(playerid, skinid);
public setPlayerSkin(playerid, skinid){
	print("Called setPlayerSkin()");
	new
	    Float:tmpPos[4],
		vehicleid = GetPlayerVehicleID(playerid),
		seatid = GetPlayerVehicleSeat(playerid);
	GetPlayerPos(playerid, tmpPos[0], tmpPos[1], tmpPos[2]);
	GetPlayerFacingAngle(playerid, tmpPos[3]);
	if(skinid < 0 || skinid > 299) return 0;
	if(GetPlayerSpecialAction(playerid) == SPECIAL_ACTION_DUCK)
	{
	    SetPlayerPos(playerid, tmpPos[0], tmpPos[1], tmpPos[2]);
		SetPlayerFacingAngle(playerid, tmpPos[3]);
		TogglePlayerControllable(playerid, 1); // preventing any freeze - optional
		return SetPlayerSkin(playerid, skinid);
	}
	else if(IsPlayerInAnyVehicle(playerid))
	{
	    new
	        tmp;
	    RemovePlayerFromVehicle(playerid);
	    SetPlayerPos(playerid, tmpPos[0], tmpPos[1], tmpPos[2]);
		SetPlayerFacingAngle(playerid, tmpPos[3]);
		TogglePlayerControllable(playerid, 1); // preventing any freeze - important - because of doing animations of exiting vehicle
		tmp = SetPlayerSkin(playerid, skinid);
		PutPlayerInVehicle(playerid, vehicleid, (seatid == 128) ? 0 : seatid);
		return tmp;
	}
	else
	{
	    return SetPlayerSkin(playerid, skinid);
	}
}

// Chat functions
getCharacterName(playerid);
getCharacterName(playerid){ // Convert name from player name
	new playername[MAX_PLAYER_NAME + 1], output[MAX_PLAYER_NAME + 1];
	GetPlayerName(playerid, playername, sizeof(playername));
	strfromliteral(output, playername);
	strreplace(output, "_", " ");

	return output;
}

forward sendToChat(playerid, target, text[128 + 1], receiver_id, DCC_User:author);
public sendToChat(playerid, target, text[128 + 1], receiver_id, DCC_User:author){
    strtrim(text);  // Trim edge whitespaces
	if(isempty(text)){
	    return 0;   // Fail if text is empty
	}
    new playername[MAX_PLAYER_NAME + 1];
    if(playerid < 0){
		DCC_GetUserName(author, playername, sizeof(playername));
		print("Hit playerid < 0");
		print(text);
	}  // Discord messagge
	else{playername = getCharacterName(playerid);}
	
	new chat_format, chat_color, chat_range, chat_userlevel, chat_character[1 + 1], chat_name[13 + 1], discord_channel;
	switch(target){
	    case CHAT_WHISPER:{
			chat_format = 2;
			chat_range = 3;
			chat_name = "whispers";
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_LOW:{
			chat_format = 2;
			chat_range = 10;
			chat_name = "speaks softly";
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
        case CHAT_LOCAL:{
            chat_format = 1;
            chat_range = 20;
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_SHOUT:{
		    chat_format = 2;
		    chat_range = 50;
			chat_name = "shouts";
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_ACTION:{
		    chat_format = 0;
		    chat_range = 20;
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
        case CHAT_OC:{
	        chat_format = 3;
            chat_range = 20;
            chat_character = "'";
            chat_name = "OOC";
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
  		case CHAT_GLOBAL:{
	  		chat_format = 3;
			chat_character = "`";
			chat_name = "Global";
			chat_color = COLOR_COMMAND_OUTPUT;
			discord_channel = ECHO_CHANNEL;
		}
		case CHAT_GANG:{
		    chat_format = 3;
			chat_character = "~";
			chat_name = "Gang";
			chat_color = COLOR_CREW_CHAT;
			// TODO: Output to gang Discord channel
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_GANG_OC:{
		    chat_format = 3;
			chat_character = "$";
			chat_name = "Gang OC";
			chat_color = COLOR_CREW_CHAT;
			// TODO: Output to gang Discord channel
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_FACTION:{
		    chat_format = 3;
			chat_character = "!";
			chat_name = "Faction";
			chat_color = COLOR_CREW_CHAT;
			// TODO: Output to faction Discord channel
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_FACTION_OC:{
		    chat_format = 3;
			chat_character = "%";
			chat_name = "Faction OC";
			chat_color = COLOR_CREW_CHAT;
			// TODO: Output to faction Discord channel
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_CREW:{
		    chat_format = 3;
			chat_character = "@";
			chat_name = "Crew";
			chat_color = COLOR_CREW_CHAT;
			chat_userlevel = MODERATOR_CREW;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_ADMIN:{
		    chat_format = 3;
			chat_character = "#";
			chat_name = "Admin";
			chat_color = COLOR_ADMIN_CHAT;
			chat_userlevel = ADMIN_CREW;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_MANAGEMENT:{
		    chat_format = 3;
			chat_character = "&";
			chat_name = "Management";
			chat_color = COLOR_ADMIN_CHAT;
			chat_userlevel = MANAGEMENT_CREW;
			discord_channel = MANAGEMENT_CHANNEL;
		}
		case CHAT_VIP:{
		    chat_format = 3;
			chat_character = "^";
			chat_name = "VIP";
			chat_color = COLOR_VIP_CHAT;
			chat_userlevel = VIP_PLAYER;
			discord_channel = ADMIN_ECHO_CHANNEL;
			//TODO: Add VIP discord channel.
		}
		case CHAT_UNDERCOVER:{
		    chat_format = -1;
			chat_character = "*";
			chat_color = COLOR_COMMAND_OUTPUT;
			chat_userlevel = ADMIN_CREW;
			discord_channel = ECHO_CHANNEL;
		}
		case CHAT_PM:{
		    chat_format = 3;
		    chat_character = ">";
		    chat_name = "PM";
			chat_color = COLOR_PM_CHAT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_CALL:{
		    chat_format = 3;
		    chat_character = "+";
		    chat_name = "Call";
			chat_color = COLOR_PM_CHAT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_SMS:{
		    chat_format = 3;
		    chat_character = "-";
		    chat_name = "SMS";
			chat_color = COLOR_PM_CHAT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
		case CHAT_RADIO:{
		    chat_format = 3;
		    chat_character = "=";
		    chat_name = "Radio";
			chat_color = COLOR_PM_CHAT;
			discord_channel = ADMIN_ECHO_CHANNEL;
		}
	}
	
	// Format chat message
	new message[9 + sizeof(chat_character) + (6 * 8) + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 128 + 1];
	new discord_message[23 + sizeof(chat_character) + sizeof(chat_name) + sizeof(playerid) + MAX_PLAYER_NAME + 128 + 1];
	switch(chat_format){
	    case -1:{
			format(message, sizeof(message), "%s %s", chat_character, text);
			format(discord_message, sizeof(discord_message), "```css\n%s %s\n```", chat_character, text);
		}
		case 0:{
			format(message, sizeof(message), EMBED_COLOR_GREY"[{%06x}%i"EMBED_COLOR_GREY"] {%06x}%s{%06x} %s", GetPlayerColor(playerid) >>> 8, playerid, GetPlayerColor(playerid) >>> 8, playername, chat_color >>> 8, text);
			format(discord_message, sizeof(discord_message), "```css\n[%i] %s %s\n```", playerid, playername, text);
		}
		case 1:{
			format(message, sizeof(message), EMBED_COLOR_GREY"[{%06x}%i"EMBED_COLOR_GREY"] {%06x}%s"EMBED_COLOR_GREY":{%06x} %s", GetPlayerColor(playerid) >>> 8, playerid, GetPlayerColor(playerid) >>> 8, playername, chat_color >>> 8, text);
			format(discord_message, sizeof(discord_message), "```css\n[%i] %s: %s\n```", playerid, playername, text);
		}
		case 2:{
			format(message, sizeof(message), EMBED_COLOR_GREY"[{%06x}%i"EMBED_COLOR_GREY"] {%06x}%s %s"EMBED_COLOR_GREY":{%06x} %s", GetPlayerColor(playerid) >>> 8, playerid, GetPlayerColor(playerid) >>> 8, playername, chat_name, chat_color >>> 8, text);
			format(discord_message, sizeof(discord_message), "```css\n[%i] %s %s: %s\n```", playerid, playername, chat_name, text);
		}
		case 3:{
			format(message, sizeof(message), "%s "EMBED_COLOR_GREY"({%06x}%s"EMBED_COLOR_GREY") [{%06x}%i"EMBED_COLOR_GREY"] {%06x}%s"EMBED_COLOR_GREY":{%06x} %s", chat_character, chat_color >>> 8, chat_name, GetPlayerColor(playerid) >>> 8, playerid, GetPlayerColor(playerid) >>> 8, playername, chat_color >>> 8, text);
			format(discord_message, sizeof(discord_message), "```css\n%s (%s) [%i] %s: %s\n```", chat_character, chat_name, playerid, playername, text);
		}// Good discord tag options for color effects: yaml, css, moon, elixir, haxe, groovy, brainfuck, accesslog,inform7, c,
	}
	
	// Authorisation
	if(chat_userlevel && chat_userlevel > GetGVarInt("userlevel", playerid)){    // User not privilged to read chat
	    if(target == CHAT_CREW){    // Show the user the message was sent to crew chat.
	        SendClientMessage(playerid, chat_color, message);
		}
		else{
		    SendClientMessage(playerid, COLOR_CREW_CHAT, "ERROR: You are not authorized to speak in this chat.");

		    return 0;   // Fail to send the message
		}
	}
	
    // Send TODO: Add checks for muted.
    if(chat_range){ // Ranged chats
        logger(LOGLEVEL_CHAT, message);	// Log event
        
        // Show message to players in range
        new Float:x, Float:y, Float:z;
		GetPlayerPos(playerid, x, y, z);
		for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){
			if(IsPlayerConnected(receiver_id)){
				new Float:distance = GetPlayerDistanceFromPoint(recipient_id, x, y, z);
				if(distance <= chat_range){  // Player nearby
		    		sendClientMultiMessage(recipient_id, chat_color, message, Bool:false);
				}
			}
		}

    	DiscordEcho(discord_message, discord_channel);
	}
	else{   // Global chats
	    if(target == CHAT_GLOBAL){  // Public chat
	        logger(LOGLEVEL_CHAT, message);	// Log event
     		sendClientMultiMessage(0, chat_color, message, Bool:true);
     		DiscordEcho(discord_message, discord_channel);
   		}
   		else if(target == CHAT_GANG || target == CHAT_GANG_OC || target == CHAT_FACTION || target == CHAT_FACTION_OC || target == CHAT_CALL || target == CHAT_SMS || target == CHAT_RADIO ){
			// TODO
			// TODO sendToAdmins();
			// TODO send to specific discord channel
			SendClientMessage(playerid, chat_color, "You are not a member of the required entity.");
		}
		else if(target == CHAT_PM){
		    new output[sizeof(message)];
			strdel(message, 0, 1);
		    format(output, sizeof(output), "<%s", message);
			sendClientMultiMessage(receiver_id, chat_color, output, Bool:false);
			
			new receipt[12 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 128 + 1];
			format(receipt, sizeof(receipt), "%s (%s) to [%i] %s: %s", chat_character, chat_name, receiver_id, playername, text);
			sendClientMultiMessage(playerid, chat_color, receipt, Bool:false);

			new admin_message[15 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 4 + 128 + 1];
			format(admin_message, sizeof(admin_message), "%s (%s) [%i] %s to [%i]: %s", chat_character, chat_name, playerid, playername, receiver_id, text);
			logger(LOGLEVEL_CHAT, admin_message);	// Log event
			
			sendToAdmins(chat_color, admin_message);
			DiscordEcho(discord_message, discord_channel);
		}
		
		else{   // Chat with select users
			for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){
				if(IsPlayerConnected(recipient_id)){
					if(GetGVarInt("userlevel", recipient_id) >= chat_userlevel){    // Privileged
					    logger(LOGLEVEL_CHAT, message);	// Log event
						sendClientMultiMessage(recipient_id, chat_color, message, Bool:false);
						DiscordEcho(discord_message, discord_channel);
					}
				}
			}
		}
	}
	return 1;
}

forward sendToAdmins(chat_color, const message[]);
public sendToAdmins(chat_color, const message[]){
    for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){
		if(IsPlayerConnected(recipient_id)){
			if(GetGVarInt("userlevel", recipient_id) >= ADMIN_CREW){    // Privileged
				SendClientMessage(recipient_id, chat_color, message);
			}
		}
	}
}

forward sendPM(playerid, text[128 + 1], recipient_id);
public sendPM(playerid, text[128 + 1], recipient_id){
	if (recipient_id == INVALID_PLAYER_ID){
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "ERROR: Player not found.");
	}
	else{
		sendToChat(playerid, CHAT_PM, text, recipient_id, DCC_INVALID_USER);
		// Todo: Send to admins and admin echo
	}
}

forward sendClientMultiMessage(recipient_id, chat_color, const message[], Bool:toall);
public sendClientMultiMessage(recipient_id, chat_color, const message[], Bool:toall){
    if(strlen(message) > 144){  // SAMP does not send messages longer than 144 characters
        new part_1[144 + 1], part_2[144 + 1];
        strmid(part_1, message, 0, 143);    // TODO see if this can be moved 1 bit
        strmid(part_2, message, 143, 286);
		if(toall){
			SendClientMessageToAll(chat_color, part_1);
			SendClientMessageToAll(chat_color, part_2);
		}
		else{
	        SendClientMessage(recipient_id, chat_color, part_1);
    	    SendClientMessage(recipient_id, chat_color, part_2);
		}
	}
	else{
	    if(toall){SendClientMessageToAll(chat_color, message);}
	    else{SendClientMessage(recipient_id, chat_color, message);}
	}
}

// Pickup functions
forward createDynamicPickup(object, Float:x, Float:y, Float:z, world, interior, pickup_type);
public createDynamicPickup(object, Float:x, Float:y, Float:z, world, interior, pickup_type){
	// Object position corrections
	if(object == 19902){
	    z = z - 0.5;
	}
	else if(object == 19607){
	    z = z - 0.5;
	}
	
	// Pickup types
	new type;
	switch(pickup_type){
		case 99999: type = 2;
		default: type = 1;
	}

    new pickupid = CreateDynamicPickup(object, type, x, y, z, world, interior);
    SetGVarInt("pickup_type", pickup_type, pickupid);
    printf("new pickupid = CreateDynamicPickup(%i, %i, %f, %f, %f, %i, %i);", pickupid, object, type, x, y, z, world, interior);
    return pickupid;
}

forward spawnPortal(id, object, Float:pos_x, Float:pos_y, Float:pos_z, world, interior, exit_object, Float:exit_pos_x, Float:exit_pos_y, Float:exit_pos_z, exit_world, exit_interior);
public spawnPortal(id, object, Float:pos_x, Float:pos_y, Float:pos_z, world, interior, exit_object, Float:exit_pos_x, Float:exit_pos_y, Float:exit_pos_z, exit_world, exit_interior){
	new pickup_id = createDynamicPickup(object, pos_x, pos_y, pos_z, world, interior, PICKUP_PORTAL);
	SetGVarFloat("pickup_x", exit_pos_x, pickup_id);
	SetGVarFloat("pickup_y", exit_pos_y, pickup_id);
	SetGVarFloat("pickup_z", exit_pos_z, pickup_id);
	SetGVarInt("pickup_world", exit_world, pickup_id);
	SetGVarInt("pickup_interior", exit_interior, pickup_id);
	new exit_pickup_id = createDynamicPickup(exit_object, exit_pos_x, exit_pos_y, exit_pos_z, exit_world, exit_interior, PICKUP_PORTAL);
	SetGVarFloat("pickup_x", pos_x, exit_pickup_id);
	SetGVarFloat("pickup_y", pos_y, exit_pickup_id);
	SetGVarFloat("pickup_z", pos_z, exit_pickup_id);
	SetGVarInt("pickup_world", world, exit_pickup_id);
	SetGVarInt("pickup_interior", interior, exit_pickup_id);

	new portal_query[64 + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
	format(portal_query, sizeof(portal_query), "UPDATE portal SET (pickup_id, exit_pickup_id) = (%i, %i) WHERE id = %i", pickup_id, exit_pickup_id, id);
	sql_query(sqlHandle, portal_query);
}

forward destroyPortal(id);
public destroyPortal(id){
    DestroyDynamicPickup(id);
	DeleteGVar("pickup_type", id);
	DeleteGVar("pickup_x", id);
	DeleteGVar("pickup_y", id);
	DeleteGVar("pickup_z", id);
	DeleteGVar("pickup_world", id);
	DeleteGVar("pickup_interior", id);
}

// Command functions
public OnPlayerCommandPerformed(playerid, cmd[], params[], result, flags){
    if(result == -1){
        SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Unknown command.");

        return 0;
    }

    return 1;
}
public OnPlayerCommandReceived(playerid, cmd[], params[], flags){
	new userlevel = GetGVarInt("userlevel", playerid);
	if(flags){
	    if(flags > userlevel){
	        SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Access denied.");
        	printf("player %d doesn’t have access to command '%s'", playerid, cmd); // TODO decide to send this to a chat or not.
        	
        	return 0;
		}
	}

    return 1;
}
public PC_OnInit(){ // TODO
    
	return 1;	// Remove this once stuff is in place.
}

flags:test(ADMIN_CREW);
cmd:test(playerid, params[]){
	SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Executed");

	return 1;
}

// Command commands
cmd:help(playerid, params[]){
    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Ask questions in the chat, on Discord or the forum.\nTo list all commands: /cmds");

    return 1;
}
alias:help("h");

cmd:cmds(playerid, params[]){
	SendClientMessage(playerid, COLOR_NOTICE, "Command help syntax: \"/\" = start of cmd, \"()\" = cmd aliasses, \"|\" = or, \"<>\" = required parameter, \"[]\" = optional parameter, \",\" = next item.");
	SendClientMessage(playerid, COLOR_NOTICE, "Command help syntax example: /command (/alias | /other_alias) <parameter> [optional_parameter], /next_command");
	SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "General commands: /help (/h), /cmds (/cmd | /commands), /my (/myself | /mine), /v (/vehivle | /veh), /p (/player)");
	SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Chat commands: /me (/emote | /action), /w (/whisper), /low (/soft), /l (/local), /s (/shout | /scream), /o (' | /oc | /ooc)");
	SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Chat commands: /g (` | /global | /public), /gc (~) /fc (!), /vc (&), /cc (@), /my chatmode <value>");
	if(GetGVarInt("userlevel", playerid) >= ADMIN_CREW){
	    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Admin commands: /property");
	}

    return 0;
}
alias:cmds("commands", "cmd");

// User commands
cmd:register(playerid, params[]){
	register(playerid);
}

cmd:my(playerid, params[]){
	// No option specified
	if(strlen(params) < 1){
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /my <option> <value>");
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Options: account chatmode");
		if(GetGVarInt("userlevel", playerid) >= ADMIN_CREW){
		    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Admin options: goto");
		}
		return 0;
	}
	
	// Options
	if(!strcmp(params, "account", true, 7)){    // /my account
		strdel(params, 0, 8);	// Remove first 8 characters, "account ", from the string.

		// Values
		if (isequal(params, "register", .ignorecase = true)){   // /my account register
			register(playerid);	// Call public register function
		}
		else if (isequal(params, "characters", .ignorecase = true)){   // /my account characters
			characterSelection(playerid);
		}
		else if (isequal(params, "deletecharacter", .ignorecase = true)){   // /my account characters
			ShowPlayerDialog(playerid, DIALOG_DELETE_CHARACTER, DIALOG_STYLE_MSGBOX, "Charater deletion", "You are about to permanently delete this charater and all it's assets.\n This can NOT be undone.", "Destroy", "Abort");
		}
		else{   // Invalid value
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /my account <value>");
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Values: register characters deletecharacter");
		}
	}
	else if(!strcmp(params, "chatmode", true, 8)){    // /my chatmode
		strdel(params, 0, 9);	// Remove first 9 characters, "account ", from the string.

        // Values
        if (isequal(params, "whisper", .ignorecase = true)){
            SetGVarInt("chatmode", CHAT_WHISPER, playerid);
		}
		else if (isequal(params, "low", .ignorecase = true)){
            SetGVarInt("chatmode", CHAT_LOW, playerid);
		}
		else if (isequal(params, "local", .ignorecase = true)){
			SetGVarInt("chatmode", CHAT_LOCAL, playerid);
		}
        else if (isequal(params, "shout", .ignorecase = true)){
            SetGVarInt("chatmode", CHAT_SHOUT, playerid);
		}
		else if (isequal(params, "oc", .ignorecase = true)){
			SetGVarInt("chatmode", CHAT_OC, playerid);
		}
		else if (isequal(params, "global", .ignorecase = true)){
			SetGVarInt("chatmode", CHAT_GLOBAL, playerid);
		}
		else if (isequal(params, "gang", .ignorecase = true)){
		    SetGVarInt("chatmode", CHAT_GANG, playerid);
		}
		else if (isequal(params, "faction", .ignorecase = true)){
		    SetGVarInt("chatmode", CHAT_FACTION, playerid);
		}
		else if (isequal(params, "vip", .ignorecase = true)){
		    SetGVarInt("chatmode", CHAT_VIP, playerid);
		}
		else if (isequal(params, "crew", .ignorecase = true)){
		    SetGVarInt("chatmode", CHAT_CREW, playerid);
		}
		else if (isequal(params, "admin", .ignorecase = true)){
            SetGVarInt("chatmode", CHAT_ADMIN, playerid);
		}
        else if (isequal(params, "management", .ignorecase = true)){
            SetGVarInt("chatmode", CHAT_MANAGEMENT, playerid);
		}
		else{   // Invalid value
			printf("else isequals register");
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /my chatmode <value>");
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Values: whisper low local shout oc global gang faction vip crew admin management");
		}
	}
	else if(!strcmp(params, "goto", true, 4) || !strcmp(params, "sendto", true, 6) || !strcmp(params, "teleport", true, 8)){
		if(GetGVarInt("userlevel", playerid) < ADMIN_CREW){
		    return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Access denied.");
		}
		
		SetGVarInt("goto_target", playerid, playerid);
		ShowPlayerDialog(playerid, DIALOG_GOTO, DIALOG_STYLE_LIST, "Categories", "Interiors\nPlayers", "Select", "Cancel");
	}
	else{   // Invalid option
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/my options: account chatmode");
		if(GetGVarInt("userlevel", playerid) >= ADMIN_CREW){
		    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Admin options: goto");
		}
	}
	return 0;
}
alias:my("myself", "mine");

// Chat commands
cmd:w(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_WHISPER, params, 0, DCC_INVALID_USER);
    
    return 1;
}
alias:w("whisper");
cmd:low(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_LOW, params, 0, DCC_INVALID_USER);

    return 1;
}
alias:low("soft");
cmd:l(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_LOCAL, params, 0, DCC_INVALID_USER);

    return 1;
}
alias:l("local");
cmd:me(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_ACTION, params, 0, DCC_INVALID_USER);

    return 1;
}
alias:me("emote", "action");
cmd:s(playerid, params[128 + 1]){
	sendToChat(playerid, CHAT_SHOUT, params, 0, DCC_INVALID_USER);

    return 1;
}
alias:s("shout", "scream");
cmd:o(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_OC, params, 0, DCC_INVALID_USER);

    return 1;
}
alias:o("oc", "ooc");
cmd:g(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_GLOBAL, params, 0, DCC_INVALID_USER);

    return 1;
}
alias:g("global", "public");
cmd:gc(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_GANG, params, 0, DCC_INVALID_USER);

    return 1;
}
cmd:fc(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_FACTION, params, 0, DCC_INVALID_USER);

    return 1;
}
cmd:vc(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_VIP, params, 0, DCC_INVALID_USER);

    return 1;
}
cmd:cc(playerid, params[128 + 1]){
    sendToChat(playerid, CHAT_CREW, params, 0, DCC_INVALID_USER);

    return 1;
}

cmd:pm(playerid, params[128 + 1]){
    new recipient_id, message[128 + 1];
	if (sscanf(params, "u s", recipient_id, message)){
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /pm <playerid> <message>");
	}
	else{
		sendPM(playerid, message, recipient_id);
	}

    return 1;
}
alias:pm("msg", "dm");

// Vehicle commands
cmd:v(playerid, params[]){
    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "health/armor [AMOUNT]");
    return 1;
}
alias:v("vehicle", "veh");

// Player commands
cmd:p(playerid, params[]){
    // No parameters
	if(strlen(params) < 1){
	    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /p <ID> option");
		return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/p options: admin, goto");
	}

	// Syntax
	new player_id, option[123 + 1];
	if(sscanf(params, "i s", player_id, option)){
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /p <ID> <option>");
		return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/p options: admin, goto");
	}
	
	if(!strcmp(option, "admin", true, 5)){
	    SetGVarInt("userlevel", ADMIN_CREW, player_id);
	    // TODO take admin if already admin
	    // TODO notify admins
	}
	else if(!strcmp(option, "goto", true, 4) && !strcmp(option, "sendto", true, 6 && !strcmp(option, "teleport", true, 8)) ){
	    if(GetGVarInt("userlevel", playerid) < ADMIN_CREW){
	        SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Access denied.");
	        
	        SetGVarInt("goto_target", player_id);
		}
	}
	else{
	    return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/p <ID> options: admin, goto");
	}
			
    return 1;
}
alias:p("player");

// Admin commands
flags:property(ADMIN_CREW);
cmd:property(playerid, params[]){
    // No option specified
	if(strlen(params) < 1){
		return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/property options: pickup, portal");
	}

	// Options
	if(!strcmp(params, "pickup", true, 6)){    // /property pickup
		strdel(params, 0, 7);	// Remove first 7 characters, "pickup ", from the string.
		
		// No parameters
		if(strlen(params) < 1){
		    SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /property pickup <create | ID <goto | move | delete>>");
			return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/p options: admin, goto");
		}
		
		// Values
        new pickup_id, option[110 + 1];
		if(!strcmp(params, "create", true)){   // /property pickup create
            ShowPlayerDialog(playerid, DIALOG_CREATE_PICKUP, DIALOG_STYLE_LIST, "Pickup caterogy", "Faction", "Select", "Cancel");
		}
		else if (!sscanf(params, "i s", pickup_id, option)){
		    new pickup_query[62 + MAX_SQL_INTEGER + 1];
			format(pickup_query, sizeof(pickup_query), "SELECT object_id, type_id, pickup_id, pos_x, pos_y, pos_z, world_id, pickup_id FROM pickup WHERE id = %i", pickup_id);
			new Result:pickup_result = sql_query(sqlHandle, pickup_query);
			if(sql_num_rows(pickup_result) < 1){   // Invalid pickup ID
			    return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Invalid pickup ID");
			}
			
			new pickupid = sql_get_field_assoc_int(pickup_result, "pickup_id");
			print(option);
            if(!strcmp(option, "move", true)){
                print("move");
                // Destroy old pickup
                DestroyDynamicPickup(pickupid);
                
                // Create new pickup
                new Float:x, Float:y, Float:z;
				GetPlayerPos(playerid, x, y, z);
				new interior = GetPlayerInterior(playerid);
				new world = GetPlayerVirtualWorld(playerid);
				
				new object_id = sql_get_field_assoc_int(Result:pickup_result, "object_id");
				new type_id = sql_get_field_assoc_int(Result:pickup_result, "type_id");
				new new_pickupid = createDynamicPickup(object_id, x, y, z, world, interior, type_id);

				// Update pickup record
				new update_query[109 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
				format(update_query, sizeof(update_query), "UPDATE pickup SET pos_x = '%f', pos_y = '%f', pos_z = '%f', world_id = %i, interior_id = %i, pickup_id = %i WHERE id = %i", x, y, z, world, interior, new_pickupid, pickup_id);
				sql_query(sqlHandle, update_query);
				
				// Inform admins
				new admin_message[39 + 4 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
				format(admin_message, sizeof(admin_message), "* [%i] %s modified the location of pickup: %i", playerid, getPlayerName(playerid), pickup_id);
				sendToAdmins(COLOR_NOTICE, admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
			}
            else if(!strcmp(option, "delete", true)){
                // Destroy pickup
                DestroyDynamicPickup(pickupid);
                
                // Delete pickup record
				new update_query[95 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
				format(update_query, sizeof(update_query), "DELETE FROM pickup WHERE id = %i", pickup_id);
				sql_query(sqlHandle, update_query);

                // Inform admins
				new admin_message[39 + 4 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
				format(admin_message, sizeof(admin_message), "* [%i] %s deleted pickup: %i", playerid, getPlayerName(playerid), pickup_id);
				sendToAdmins(COLOR_NOTICE, admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
			}
			else if(!strcmp(option, "goto", true)){
				SetPlayerPos(playerid, sql_get_field_assoc_int(Result:pickup_result, "pos_x"), sql_get_field_assoc_int(Result:pickup_result, "pos_y"), sql_get_field_assoc_int(Result:pickup_result, "pos_z"));
				SetPlayerVirtualWorld(playerid, sql_get_field_assoc_int(Result:pickup_result, "world_id"));
				SetPlayerInterior(playerid, sql_get_field_assoc_int(Result:pickup_result, "interior_id"));
			}
			else{   // Invalid option.
				SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/property pickup <ID> usage: goto, move, delete");
			}
		}
		else{   // Invalid value
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/property pickup usage: create, <ID> <goto | move | delete>");
		}
	}
	else if(!strcmp(params, "portal", true, 6)){    // /property portal
		strdel(params, 0, 7);	// Remove first 7 characters, "portal ", from the string.

        // No parameters
		if(strlen(params) < 1){
			return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /property portal <create | ID <goto | exit | entrance | object | delete>>");
		}
		
        // Values
        new portal_id, option[116 + 1];
		if (!strcmp(params, "create", true, 6)){   // /property portal create
            strdel(params, 0, 7);	// Remove first 7 characters, "create ", from the string.

			// Worlds
			new world[105 + 1], world_id;
            if (sscanf(params, "s", world)){
				SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "For a portal that works in all worlds use: /property portal create all");
			}
			if(!strcmp(world, "all", true)){
			    world_id = -1;
			}
			else{
				world_id = GetPlayerVirtualWorld(playerid);
				SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Invalid world option, creating default portal.");
			}

			new Float:x, Float:y, Float:z;
			GetPlayerPos(playerid, x, y, z);
			new interior = GetPlayerInterior(playerid);
			
			new pickup_id = createDynamicPickup(19902, x, y, z, world_id, interior, PICKUP_PORTAL);

			// Create portal database record
			new portal_query[111 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_INTEGER + 1];
			format(portal_query, sizeof(portal_query), "INSERT INTO portal(object, pos_x, pos_y, pos_z, world, pickup_id, interior_id) VALUES (19902, '%f', '%f', '%f', %i, %i, %i)", x, y, z, world_id, pickup_id, interior);
			sql_query(sqlHandle, portal_query);
			
			// Inform user(s)
			new portal_id_query[91 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_INTEGER + 1];
			format(portal_id_query, sizeof(portal_id_query), "SELECT id FROM portal WHERE pos_x = '%f' AND pos_y = '%f' AND pos_z = '%f' AND world = %i AND interior_id = %i", x, y, z, world_id, interior);
			new Result:portal_id_result = sql_query(sqlHandle, portal_id_query);
			portal_id = sql_get_field_assoc_int(Result:portal_id_result, "id");
			new message[69 + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1], admin_message[21 + 4 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
			format(message, sizeof(message), "Portal %i created. Create the other side with: /property portal %i exit", portal_id, portal_id);
			format(admin_message, sizeof(admin_message), "* [%i] %s created portal: %i", playerid, getPlayerName(playerid), portal_id);
			sendToAdmins(COLOR_NOTICE , admin_message);
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, message);
			DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
		}
        else if (!sscanf(params, "i s", portal_id, option)){
            // Get portal record
   			new portal_query[171 + MAX_SQL_INTEGER + 1];
   			format(portal_query, sizeof(portal_query), "SELECT object, pos_x, pos_y, pos_z, world, interior_id, pickup_id, exit_pos_x, exit_pos_y, exit_pos_z, exit_world, exit_interior_id, exit_pickup_id FROM portal WHERE id = %i", portal_id);
   			new Result:portal_result = sql_query(sqlHandle, portal_query);
   			
   			// Invalid portal ID
   			if(sql_num_rows(portal_result) < 1){   // Invalid portal ID
			    return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Invalid portal ID");
			}
   			
            if (!strcmp(option, "exit", true, 4)){
				new Float:pos_x = sql_get_field_assoc_float(portal_result, "pos_x");
				new Float:pos_y = sql_get_field_assoc_float(portal_result, "pos_y");
				new Float:pos_z = sql_get_field_assoc_float(portal_result, "pos_z");
				new world_id = sql_get_field_assoc_int(portal_result, "world");
				new interior_id = sql_get_field_assoc_int(portal_result, "interior_id");
				new session_id = sql_get_field_assoc_int(portal_result, "pickup_id");
				new exit_pickup_id = sql_get_field_assoc_int(portal_result, "exit_pickup_id");
				

				// World
				new world;
				if(world_id != -1){   // Not an all-world portal
				    world = GetPlayerVirtualWorld(playerid);
				}
				else{
				    world = world_id;
				}

	            // Location
				new Float:x, Float:y, Float:z;
				GetPlayerPos(playerid, x, y, z);
				new interior = GetPlayerInterior(playerid);

				// Delete old exit if exists
				if(exit_pickup_id){
				     destroyPortal(exit_pickup_id);
				}

	            new pickup_id = createDynamicPickup(19607, x, y, z, world, interior, PICKUP_PORTAL);
	            SetGVarFloat("pickup_x", pos_x, pickup_id);
				SetGVarFloat("pickup_y", pos_y, pickup_id);
				SetGVarFloat("pickup_z", pos_z, pickup_id);
				SetGVarInt("pickup_world", world_id, pickup_id);
				SetGVarInt("pickup_interior", interior_id, pickup_id);
				SetGVarFloat("pickup_x", x, session_id);
				SetGVarFloat("pickup_y", y, session_id);
				SetGVarFloat("pickup_z", z, session_id);
				SetGVarInt("pickup_world", world, session_id);
				SetGVarInt("pickup_interior", interior, session_id);

				// Update portal record
				new update_query[155 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
				format(update_query, sizeof(update_query), "UPDATE portal SET exit_object = 19607, exit_pos_x = '%f', exit_pos_y = '%f', exit_pos_z = '%f', exit_world = %i, exit_interior_id = %i, exit_pickup_id = %i WHERE id = %i", x, y, z, world, interior, pickup_id, portal_id);
				sql_query(sqlHandle, update_query);

				// Inform admins and Discord
				new admin_message[61 + 4 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
				format(admin_message, sizeof(admin_message), "* [%i] %s modified the exit location of portal: %i", playerid, getPlayerName(playerid), portal_id);
				sendToAdmins(COLOR_NOTICE , admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
			}
			else if(!strcmp(option, "object", true, 6)){
			    new object, exit_object;
				if (sscanf(option, "i i", object, exit_object)){
				    return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Invalid syntax, specify two object ID's: /property portal <ID> object <ID> <ID>");
				}

				new Float:pos_x = sql_get_field_assoc_float(portal_result, "pos_x");
				new Float:pos_y = sql_get_field_assoc_float(portal_result, "pos_y");
				new Float:pos_z = sql_get_field_assoc_float(portal_result, "pos_z");
				new world_id = sql_get_field_assoc_int(portal_result, "world");
				new interior_id = sql_get_field_assoc_int(portal_result, "interior_id");
				new pickup_id = sql_get_field_assoc_int(portal_result, "pickup_id");
				new Float:exit_pos_x = sql_get_field_assoc_float(portal_result, "exit_pos_x");
				new Float:exit_pos_y = sql_get_field_assoc_float(portal_result, "exit_pos_y");
				new Float:exit_pos_z = sql_get_field_assoc_float(portal_result, "exit_pos_z");
				new exit_world_id = sql_get_field_assoc_int(portal_result, "exit_world");
				new exit_interior_id = sql_get_field_assoc_int(portal_result, "exit_interior_id");
				new exit_pickup_id = sql_get_field_assoc_int(portal_result, "exit_pickup_id");

	            destroyPortal(exit_pickup_id);
				destroyPortal(pickup_id);
				spawnPortal(portal_id, object, pos_x, pos_y, pos_z, world_id, interior_id, exit_object, exit_pos_x, exit_pos_y, exit_pos_z, exit_world_id, exit_interior_id);

				new update_query[57 + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
				format(update_query, sizeof(update_query), "UPDATE portal SET (object, exit_object) = (%i, %i) WHERE id = %i", object, exit_object, portal_id);
				sql_query(sqlHandle, portal_query);

			}
			else if(!strcmp(option, "delete", true, 6)){
				new pickup_id = sql_get_field_assoc_int(portal_result, "pickup_id");
				new exit_pickup_id = sql_get_field_assoc_int(portal_result, "exit_pickup_id");

	   			// TODO: Confirmation, by spectating the pickup or having to be near.

	   			destroyPortal(exit_pickup_id);
				destroyPortal(pickup_id);

				// Delete portal record
				new delete_query[31 + MAX_SQL_INTEGER + 1];
				format(delete_query, sizeof(delete_query), "DELETE FROM portal WHERE id = %i", portal_id);
				sql_query(sqlHandle, delete_query);
			}
			else if(!strcmp(option, "entrance", true, 8)){
	   			new object = sql_get_field_assoc_int(portal_result, "object");
				//new Float:x = sql_get_field_assoc_float(portal_result, "pos_x");
				//new Float:y = sql_get_field_assoc_float(portal_result, "pos_y");
				//new Float:z = sql_get_field_assoc_float(portal_result, "pos_z");  todo shorten qeury
				new world = sql_get_field_assoc_int(portal_result, "world");
				new pickup_id = sql_get_field_assoc_int(portal_result, "pickup_id");
				new Float:pos_x = sql_get_field_assoc_float(portal_result, "exit_pos_x");
				new Float:pos_y = sql_get_field_assoc_float(portal_result, "exit_pos_y");
				new Float:pos_z = sql_get_field_assoc_float(portal_result, "exit_pos_z");
				new world_id = sql_get_field_assoc_int(portal_result, "exit_world");
				new interior_id = sql_get_field_assoc_int(portal_result, "exit_interior");
				if(sql_num_rows(portal_result) < 1){   // Invalid portal ID
				    return SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Invalid portal ID");
				}

				// World
				if(world_id != -1){   // Not an all-world portal
				    world = GetPlayerVirtualWorld(playerid);
				}
				else{
				    world = world_id;
				}

	            // Location
				new Float:x, Float:y, Float:z;
				GetPlayerPos(playerid, x, y, z);
				new interior = GetPlayerInterior(playerid);

	   			destroyPortal(pickup_id);   // Despawn old pickups and remove GVars

	            new pickupid = createDynamicPickup(object, x, y, z, world, interior, PICKUP_PORTAL);
	            SetGVarFloat("pickup_x", pos_x, pickupid);
				SetGVarFloat("pickup_y", pos_y, pickupid);
				SetGVarFloat("pickup_z", pos_z, pickupid);
				SetGVarInt("pickup_world", world_id, pickupid);
				SetGVarInt("pickup_interior", interior_id, pickupid);

				// Inform admins and Discord
				new admin_message[65 + 4 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
				format(admin_message, sizeof(admin_message), "* [%i] %s modified the entrance location of portal: %i", playerid, getPlayerName(playerid), portal_id);
				sendToAdmins(COLOR_NOTICE, admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
			}
			else if(!strcmp(option, "goto", true)){
				SetPlayerPos(playerid, sql_get_field_assoc_int(Result:portal_result, "exit_pos_x"), sql_get_field_assoc_int(Result:portal_result, "exit_pos_y"), sql_get_field_assoc_int(Result:portal_result, "exit_pos_z"));
				SetPlayerVirtualWorld(playerid, sql_get_field_assoc_int(Result:portal_result, "exit_world_id"));
				SetPlayerInterior(playerid, sql_get_field_assoc_int(Result:portal_result, "exit_interior_id"));
			}
			else{   // Invalid option
				SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/property portal <ID> options: goto, exit, entrance, object, delete");
			}
		}
		else{   // Invalid value
			SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /property portal <create | ID <goto | exit | entrance | object | delete>>");
		}

	}
	else{   // Invalid option
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "/property options: pickup, portal");
	}
    
    return 1;
}

// Dialog functions
forward dialogGotoInterior(playerid);
public dialogGotoInterior(playerid){
    ShowPlayerDialog(playerid, DIALOG_GOTO_INTERIOR, DIALOG_STYLE_LIST, "Categories", "24/7's\nAvaition\nAmmunation's\nBurglary houses\nMissions\nMission exteriors\nMission houses\nModding shops\nPolice departments\nSafe houses\nShops & casino's\nStadia", "Select", "Back");
}

forward dialogGotoPlayer(playerid);
public dialogGotoPlayer(playerid){
    for(new targetid, a = GetMaxPlayers(); targetid < a; targetid++){
		if(IsPlayerConnected(targetid)){
		    // TODO: Decide to use this or TABplayer list.
		}
	}

    ShowPlayerDialog(playerid, DIALOG_GOTO_PLAYER, DIALOG_STYLE_LIST, "Players", "", "Goto", "Back");
}

// a_samp events
public OnGameModeInit(){
	new message[36 + 22 + 1];
	format(message, sizeof(message), "* Global game-mode initialization: v%s", MODE_NAME);
	logger(LOGLEVEL_NOTICE, "* Global game-mode initialization.");	// Log event.

	// Player radar blip markers only visible to nearby players.
	ShowPlayerMarkers(PLAYER_MARKERS_MODE_STREAMED);
	LimitPlayerMarkerRadius(15);
	

	// Set mode name
	SetGameModeText(MODE_NAME);

	// SQL log level
	if(scriptDebug){
		sql_debug(LOG_ALL, LOG_ALL);	// Log everything everywhere.
	}
	else{
	    sql_debug(LOG_INFO, LOG_WARNING);    // Loglevel info for file and worning for console.
	}

	// Connect to database
	sqlHandle = SQL:sql_connect(SQL_HANDLER_POSTGRESQL, PG_HOST, PG_ROLE, PG_PASS, PG_DB, PG_PORT);
	printf("sqlconnection = %d", _:sqlHandle);
	if(!sql_ping(sqlHandle)){
		print( " + Database connection" );
	}
    else{
		print( "Database connection failed" );
	}

    // Initialize Discord
	//homeGuild = DCC_GetGuildId(DISCORD_HOME_GUILD_ID);	// Set home guild ID.	NOT NEEDED FOR NOW AND BROKEN
	echoChannel = DCC_FindChannelById(DISCORD_ECHO_CHANNEL_ID);	// Set main echo channel ID.
	mainChannel = DCC_FindChannelById(DISCORD_MAIN_CHANNEL_ID);	// Set main notification channel ID.
	adminEchoChannel = DCC_FindChannelById(DISCORD_ADMIN_ECHO_CHANNEL_ID);	// Set admin echo channel ID.
	adminChannel = DCC_FindChannelById(DISCORD_ADMIN_CHANNEL_ID);	// Set admin notification channel ID.
	managementChannel = DCC_FindChannelById(DISCORD_MANAGEMENT_CHANNEL_ID);	// Set management notification channel ID.
	
	DiscordEcho(message, ECHO_CHANNEL); // Notify Discord
	//DiscordEcho(message, MAIN_CHANNEL);   // TODO Enable after we are stable
	
	// Portals
   	new Result:portal_result = sql_query(sqlHandle, "SELECT id, object, pos_x, pos_y, pos_z, world, interior_id, exit_object, exit_pos_x, exit_pos_y, exit_pos_z, exit_world, exit_interior_id FROM portal");
   	for(new i = 0; i < sql_num_rows(portal_result); i++){
	   	new id = sql_get_field_assoc_int_ex(portal_result, i, "id");
		new object = sql_get_field_assoc_int_ex(portal_result, i, "object");
   	    new Float:pos_x = sql_get_field_assoc_float_ex(portal_result, i, "pos_x");
		new Float:pos_y = sql_get_field_assoc_float_ex(portal_result, i, "pos_y");
		new Float:pos_z = sql_get_field_assoc_float_ex(portal_result, i, "pos_z");
		new world = sql_get_field_assoc_int_ex(portal_result, i, "world");
		new interior = sql_get_field_assoc_int_ex(portal_result, i, "interior_id");
		new exit_object = sql_get_field_assoc_int_ex(portal_result, i, "exit_object");
		new Float:exit_pos_x = sql_get_field_assoc_float_ex(portal_result, i, "exit_pos_x");
		new Float:exit_pos_y = sql_get_field_assoc_float_ex(portal_result, i, "exit_pos_y");
		new Float:exit_pos_z = sql_get_field_assoc_float_ex(portal_result, i, "exit_pos_z");
		new exit_world = sql_get_field_assoc_int_ex(portal_result, i, "exit_world");
		new exit_interior = sql_get_field_assoc_int_ex(portal_result, i, "exit_interior_id");
		
		spawnPortal(id, object, pos_x, pos_y, pos_z, world, interior, exit_object, exit_pos_x, exit_pos_y, exit_pos_z, exit_world, exit_interior);
   	}
   	
   	// Pickups
   	new Result:pickup_result = sql_query(sqlHandle, "SELECT id, object_id, pos_x, pos_y, pos_z, world_id, interior_id, type_id FROM pickup");
   	for(new i = 0; i < sql_num_rows(pickup_result); i++){
   	    new id = sql_get_field_assoc_int(pickup_result, "id");
		new object_id = sql_get_field_assoc_int_ex(pickup_result, i, "object_id");
   	    new Float:pos_x = sql_get_field_assoc_float_ex(pickup_result, i, "pos_x");
		new Float:pos_y = sql_get_field_assoc_float_ex(pickup_result, i, "pos_y");
		new Float:pos_z = sql_get_field_assoc_float_ex(pickup_result, i, "pos_z");
		new world_id = sql_get_field_assoc_int_ex(pickup_result, i, "world_id");
		new interior_id = sql_get_field_assoc_int_ex(pickup_result, i, "interior_id");
		new type_id = sql_get_field_assoc_int_ex(pickup_result, i, "type_id");
		
		new pickupid = createDynamicPickup(object_id, pos_x, pos_y, pos_z, world_id, interior_id, type_id);
		printf("On connect pickupid: %i", pickupid);

		// Update pickup record
		new pickup_query[44 + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
		format(pickup_query, sizeof(pickup_query), "UPDATE pickup SET pickup_id = %i WHERE id = %i", pickupid, id);
		sql_query(sqlHandle, pickup_query);
   	}
	

	// Hobo's with a cane (0 ammo value makes them lose the cane as soon as they switch weapon)
	// Only homeless skins, as players should slowly class up in society.
	AddPlayerClass(134, -184.7607, 950.5010, 16.7740, 358.3032, 15, 0, 0, 0, 0, false);		// Fort Carson West boulevard right curb.
	AddPlayerClass(10, -184.7607, 950.5010, 16.7740, 358.3032, 15, 0, 0, 0, 0, false);		// Fort Carson West boulevard right curb.
	AddPlayerClass(78, 111.0115, 1189.2029, 18.1627, 89.0095, 15, 0, 0, 0, 0, false);		// Fort Carson South boulevard left curb.
	AddPlayerClass(129, 111.0115, 1189.2029, 18.1627, 89.0095, 15, 0, 0, 0, 0, false);		// Fort Carson South boulevard left curb.
	AddPlayerClass(162, -109.4227, 1242.4860, 16.8223, 183.5798, 15, 0, 0, 0, 0, false);	// Fort Carson East boulevard left curb.
	AddPlayerClass(77, -109.4227, 1242.4860, 16.8223, 183.5798, 15, 0, 0, 0, 0, false);		// Fort Carson East boulevard left curb.
	AddPlayerClass(79, -201.5379, 948.1683, 15.9131, 359.9720, 15, 0, 0, 0, 0, false); 		// Fort Carson West boulevard left curb.
	AddPlayerClass(196, -201.5379, 948.1683, 15.9131, 359.9720, 15, 0, 0, 0, 0, false); 	// Fort Carson West boulevard left curb.
	AddPlayerClass(239, 62.4694, 1205.0531, 18.8153, 89.9380, 15, 0, 0, 0, 0, false); 		// Fort Carson South boulevard right curb.
	AddPlayerClass(89, 62.4694, 1205.0531, 18.8153, 89.9380, 15, 0, 0, 0, 0, false); 		// Fort Carson South boulevard right curb.
	AddPlayerClass(135, -126.0831, 1242.5745, 18.6138, 183.2986, 15, 0, 0, 0, 0, false); 	// Fort Carson East boulevard right curb.
	AddPlayerClass(197, -126.0831, 1242.5745, 18.6138, 183.2986, 15, 0, 0, 0, 0, false); 	// Fort Carson East boulevard right curb.

	return 1;
}

public OnGameModeExit(){
	// Cycle every player
	for(new playerid, a = GetMaxPlayers(); playerid < a; playerid++){
		if(IsPlayerConnected(playerid)){
			// Set name back to username
			//new client_connect_username[MAX_PLAYER_NAME + 1];
			//GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);
			//SetPlayerName(playerid, client_connect_username);	// Change name in-game back to username, for login after restart  PROBLEM: Crashses the server on GMX.

			deleteAllGVars(playerid);   // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076
			// TODO think of somthing for the usernames, kickign every player, or accapting character names as usersnames.
        }
	}

    logger(LOGLEVEL_NOTICE, "* Global game-mode termination.");	// Log event
	sql_wait(sqlHandle);	// Wait for queries to finish.
	sql_disconnect(sqlHandle);	// Disconnect from database.
	DiscordEcho("* Global game-mode termination.", ECHO_CHANNEL);	// Notify discord
	//DiscordEcho("* Global game-mode termination.", MAIN_CHANNEL);	// Enable when stable
	
	return 1;
}

public OnPlayerRequestClass(playerid, classid){	// Skin selection before spawn.
	if(scriptDebug){
	    new message[23 + 4 + MAX_PLAYER_NAME + 1];
		format(message, sizeof(message), "* [%i] %s Class selection.", playerid, getCharacterName(playerid));

		logger(LOGLEVEL_DEBUG, message);	// Log event.
	}

	SetPlayerPos(playerid, -185.5514, 944.2042, 15.9337);	// In front of Fort Carson city limits sign.
	SetPlayerFacingAngle(playerid, 182.7345);	// Charater looks toward the camera.
	SetPlayerCameraPos(playerid, -185.5514, 939.0957, 15.6594);	// Further in front of the Fort Carson city limits sign.
	SetPlayerCameraLookAt(playerid, -185.5514, 944.2042, 15.9337);	// In front of Fort Carson city limits sign.

	return 1;   // Must return one, or skin selection breaks
}

public OnPlayerConnect(playerid){
	SetPlayerColor(playerid, COLOR_BLACK);
	
	// Create & populate variables
	new playername[MAX_PLAYER_NAME + 1], playerip[MAX_SQL_IP + 1];
	playername = getPlayerName(playerid);
	GetPlayerIp(playerid, playerip, sizeof(playerip));

	// Global connection notification
	new admin_message[23 + MAX_PLAYER_NAME + MAX_SQL_IP + 1];
	format(admin_message, sizeof(admin_message), "* [%d] %s (IP: %s) connected.", playerid, getCharacterName(playerid), playerip);
	logger(LOGLEVEL_INFO, admin_message);	// Log event
    sendToAdmins(COLOR_NOTICE, admin_message);	// Notify all admins.
	DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);	// Notify Discord admin echo.

	// Create IP record or update connection attempts
	new ip_query[109 + MAX_SQL_IP + 1];
	format(ip_query, sizeof(ip_query), "INSERT INTO ip(address) VALUES('%s') ON CONFLICT (address) DO UPDATE SET connections = ip.connections+1", playerip);
	sql_query(sqlHandle, ip_query);
	
	// Check if IP is banned
	new ip_id = getIPID(playerid);
	
	new ban_query[75 + MAX_SQL_INTEGER + 1];
	format(ban_query, sizeof(ban_query), "SELECT id, reason FROM ip_ban WHERE ip_id = %i AND expires > NOW()::timestamp", ip_id);
	new Result:ban_result = sql_query(sqlHandle, ban_query);
	if(sql_num_rows(ban_result) > 0){  // Banned
	    new kick_message[8 + MAX_SQL_REASON + 1];
	    format(kick_message, sizeof(kick_message), "Banned: %s", sql_get_field_assoc_int(ban_result, "reason"));
	    kickPlayer(playerid, -1, kick_message, 1);
	}

    // Get user record
	new escaped_username[MAX_PLAYER_NAME + 1], user_query[47 + MAX_PLAYER_NAME + 1];    // Should be longer to allow for escaped characters
	sql_escape_string(sqlHandle, playername, escaped_username, sizeof(escaped_username));	// Escape player name
	format(user_query, sizeof(user_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username);
	//new Result:result = sqlQuery(sqlHandle, user_query);  // Middleware broken.
	new Result:result = sql_query(sqlHandle, user_query);
	
	SetGVarString("client_connect_username", playername, playerid); // Used by register, DIALOG_LOGIN, getUserID & OnGameModeExit
	
	if(sql_num_rows(result) == 0){  // Unkown user
		SetGVarInt("userlevel", UNREGISTERED_PLAYER, playerid);	// Set userlevel unregistered
		changeName(playerid, playername);	// Check, force and set role-play name
	}
	else{   // Known user
		ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Sign in", "Enter your password to log on", "Log in", "Cancel");	// Show password confirmation dialog
	}

	//return 1;
}

public OnPlayerDisconnect(playerid, reason){
    if(GetPlayerState(playerid) != PLAYER_STATE_NONE){  // Only save character if spawned
		savePlayerState(playerid);  // Save character
	}
	deleteAllGVars(playerid);   // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076
	
	new playername[MAX_PLAYER_NAME + 1], message[40 + 4 + MAX_PLAYER_NAME + 1];
	playername = getPlayerName(playerid);
    switch(reason){
		case 0: format(message, sizeof(message), "* [%i] %s disconnected. (Lost Connection)", playerid, getCharacterName(playerid));
		case 1: format(message, sizeof(message), "* [%i] %s disconnected. (Leaving)", playerid, getCharacterName(playerid));
		case 2: format(message, sizeof(message), "* [%i] %s disconnected. (Kicked)", playerid, getCharacterName(playerid)); // Leave this in place for RCON kicks.
	}

 	logger(LOGLEVEL_INFO, message);	// Log event
	SendClientMessageToAll(COLOR_NOTICE, message);	// Notify all players.
	DiscordEcho(message, ECHO_CHANNEL);	// Notify discord.
	
	return 1;
}

public OnPlayerSpawn(playerid){
	if(scriptDebug){	// Log event in case of debugging.
		new message[MAX_PLAYER_NAME + 14 + 1];
		format(message, sizeof(message), "* [%i] %s spawned.", playerid, getCharacterName(playerid));

		logger(LOGLEVEL_DEBUG, message);	// Log event.
	}

	if(GetGVarInt("userlevel", playerid) == UNREGISTERED_PLAYER){	// Unregistered player.
		SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: To reserve your name, save your character, statistics and money, type: /register");
	}

	return 1;
}

public OnPlayerDeath(playerid, killerid, reason){
	new message[15 + MAX_PLAYER_NAME + 1];
	format(message, sizeof(message), "* [%d] %s died.", playerid, getCharacterName(playerid));	// TODO Add killerid & reason.

	logger(LOGLEVEL_DEBUG, message);	// Log event
	DiscordEcho(message, ADMIN_ECHO_CHANNEL);	// Notify Discord admin echo.
	
	//ResetPlayerMoney(playerid);   TODO test if required
	//SpawnPlayer(playerid);

	return 1;
}

public OnVehicleSpawn(vehicleid)	// TODO for 0.0a
{
	return 1;
}

public OnVehicleDeath(vehicleid, killerid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerText(playerid, text[]){
	new shortcut_message[128 + 1];
	strfromliteral(shortcut_message, text);
	strdel(shortcut_message, 0, 1); // Remove chat character from text
	switch(strgetfirstc(text)){
		case '`': {
		    if(GetGVarInt("chatmode", playerid) == CHAT_LOCAL){
		        sendToChat(playerid, CHAT_GLOBAL, shortcut_message, 0, DCC_INVALID_USER);
			}
			else if (GetGVarInt("chatmode", playerid) == CHAT_GLOBAL){
				sendToChat(playerid, CHAT_LOCAL, shortcut_message, 0, DCC_INVALID_USER);
			}
			else{
			    sendToChat(playerid, CHAT_GLOBAL, shortcut_message, 0, DCC_INVALID_USER);
			}
		}
		case '~': {sendToChat(playerid, CHAT_GANG, shortcut_message, 0, DCC_INVALID_USER);}
		case '!': {sendToChat(playerid, CHAT_FACTION, shortcut_message, 0, DCC_INVALID_USER);}
		case '@': {sendToChat(playerid, CHAT_CREW, shortcut_message, 0, DCC_INVALID_USER);}
		case '#': {sendToChat(playerid, CHAT_ADMIN, shortcut_message, 0, DCC_INVALID_USER);}
		case '$': {sendToChat(playerid, CHAT_GANG_OC, shortcut_message, 0, DCC_INVALID_USER);}
		case '%': {sendToChat(playerid, CHAT_FACTION_OC, shortcut_message, 0, DCC_INVALID_USER);}
		case '^': {sendToChat(playerid, CHAT_MANAGEMENT, shortcut_message, 0, DCC_INVALID_USER);}
		//case '$': {sendToChat(playerid, CHAT_MANAGEMENT, shortcut_message[]);}
		case '&': {sendToChat(playerid, CHAT_VIP, shortcut_message, 0, DCC_INVALID_USER);}
		case '*': {sendToChat(playerid, CHAT_UNDERCOVER, shortcut_message, 0, DCC_INVALID_USER);}
		case '+': {sendToChat(playerid, CHAT_CALL, shortcut_message, 0, DCC_INVALID_USER);}
		case '-': {sendToChat(playerid, CHAT_SMS, shortcut_message, 0, DCC_INVALID_USER);}
		case '=': {sendToChat(playerid, CHAT_RADIO, shortcut_message, 0, DCC_INVALID_USER);}
		case '>': {
		    new recipient_id, message[128 + 1];
    		if (sscanf(shortcut_message, "u s", recipient_id, message)){
				SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: > <playerid> <message>");
			}
			else{
				sendPM(playerid, message, recipient_id);
			}
		}
		default:{
			new	chatmode = GetGVarInt("chatmode", playerid), message[128 + 1];
			strmid(message, text, 0, 128);
//			message = text;
			if(!chatmode){  // Default is local chat
				sendToChat(playerid, CHAT_LOCAL, message, 0, DCC_INVALID_USER);
			}
			else{   // Send to preferred chat
			    printf("chatmode: %i", chatmode);
			    sendToChat(playerid, chatmode, message, 0, DCC_INVALID_USER);
			}
		}
 	}

	return 0;   // Return 1 for default behavior, return 0 to disable default output.
}

public OnPlayerCommandText(playerid, cmdtext[]){
	if(scriptDebug){	// Log event in case of debugging.
		new message[5 + 4 + MAX_PLAYER_NAME + 128 + 1]; // 128 = samp text input limit.
		format(message, sizeof(message), "[%d] %s: %s", playerid, getCharacterName(playerid), cmdtext);

		logger(LOGLEVEL_COMMAND, message);	// Log event
	}

	return 0;
}

public OnPlayerEnterVehicle(playerid, vehicleid, ispassenger)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerExitVehicle(playerid, vehicleid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerStateChange(playerid, newstate, oldstate) // TODO for 0.0a
{
	return 1;
}

public OnPlayerEnterCheckpoint(playerid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerLeaveCheckpoint(playerid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerEnterRaceCheckpoint(playerid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerLeaveRaceCheckpoint(playerid)	// TODO for 0.0a
{
	return 1;
}

public OnRconCommand(cmd[]){    // The website and some cronjobs do RCON commands. TODO create filter not to show some commands to preven spam
	new message[8 + 128 + 1];	// Max samp message legnth = 128.
	format(message, sizeof(message), "* RCON: %s", cmd);

	logger(LOGLEVEL_NOTICE, message);	// Log event
	DiscordEcho(message, MANAGEMENT_CHANNEL);

	return 1;
}

public OnPlayerRequestSpawn(playerid){  // After picking a skin in class selection.
    SetPlayerColor(playerid, COLOR_WHITE);
    
	new playername[MAX_PLAYER_NAME + 1], message[14 + MAX_PLAYER_NAME + 1];
	playername = getPlayerName(playerid);

	if(scriptDebug){	// Only log spawns when debuggins script.
		format(message, sizeof(message), "* [%d] %s Chose a character.", playerid, getCharacterName(playerid));
		logger(LOGLEVEL_DEBUG, message);	// Log event
	}

	if(GetGVarInt("userlevel", playerid) > UNREGISTERED_PLAYER){    // Registered player
	    createCharacterRecord(playerid, getUserID(playerid));	// Save character
	    
	    new client_connect_username[MAX_PLAYER_NAME + 1], admin_message[25 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
	    GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);   // Get client connect name
	    format(admin_message, sizeof(admin_message), "[%i] %s has been created by %s.", playerid, getCharacterName(playerid), client_connect_username);
	    DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
	}

	return 1;
}

public OnObjectMoved(objectid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerObjectMoved(playerid, objectid)	// TODO for 0.0a
{
	return 1;
}

//public OnPlayerPickUpPickup(playerid, pickupid){} // Replaced byOnPlayerPickUpDynamicPickup() streamer.inc

public OnPlayerPickUpDynamicPickup(playerid, pickupid){ // Requires streamer

	new type = GetGVarInt("pickup_type", pickupid);
	// Special pickup types first, rugular in the else{} conditional statement
	if(type == PICKUP_PORTAL){
   		// Do nothing if the player just used a pickup
		if(GetGVarInt("disable_pickups", playerid)){
            return 0;
		}

		new Float:x = GetGVarFloat("pickup_x", pickupid);
		new Float:y = GetGVarFloat("pickup_y", pickupid);
		new Float:z = GetGVarFloat("pickup_z", pickupid);
		new world = GetGVarInt("pickup_world", pickupid);
		new interior = GetGVarInt("pickup_interior", pickupid);
		// Don't releport when the exit is not set
		if(x == 0 && y == 0 && z == 0){
		    return 0;
		}

		// Create dynamic area and temporarily disable pickups for player
		new area = CreateDynamicCircle(x, y, 1.7, world, -1, playerid);
		SetGVarInt("disable_pickups", area, playerid);

		// Teleport player
		SetPlayerPos(playerid, x, y, z);
		if(world != -1){SetPlayerVirtualWorld(playerid, world);}
		SetPlayerInterior(playerid, interior);
	}
	else{
        // Do nothing if the player just used a pickup
		if(GetGVarInt("disable_pickups", playerid)){
            return 0;
		}
		
		// Create dynamic area and temporarily disable pickups for player
		new Float:x, Float:y, Float:z;
		GetPlayerPos(playerid, x, y, z);
		new area = CreateDynamicCircle(x, y, 1, GetPlayerVirtualWorld(playerid), -1, playerid);
		SetGVarInt("disable_pickups", area, playerid);
		
		// Regular pickup types
		switch(type){
	        case PICKUP_FACTION_PD: {
				switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{ShowPlayerDialog(playerid, DIALOG_DUTY_PD, DIALOG_STYLE_MSGBOX, "Active police duty", "Go on police duty as a rookie cop?", "Yes", "No");}
				    case JOB_POLICE:{ShowPlayerDialog(playerid, DIALOG_DUTY_PD, DIALOG_STYLE_MSGBOX, "Active police duty", "Go off police duty?", "Yes", "No");}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}

			}
			case PICKUP_FACTION_MD:{
			    switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{ShowPlayerDialog(playerid, DIALOG_DUTY_MD, DIALOG_STYLE_MSGBOX, "Active medic duty", "Go on medic duty as a rookie paramedic?", "Yes", "No");}
				    case JOB_MEDIC:{ShowPlayerDialog(playerid, DIALOG_DUTY_MD, DIALOG_STYLE_MSGBOX, "Active medic duty", "Go off paramedic duty?", "Yes", "No");}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you have another job.");}
				}
			}
			case PICKUP_FACTION_FD:{
			    printf("jobid: %i", GetGVarInt("job", playerid));
			    switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{ShowPlayerDialog(playerid, DIALOG_DUTY_FD, DIALOG_STYLE_MSGBOX, "Active fire duty", "Go on fire duty as a rookie firefighter?", "Yes", "No");}
				    case JOB_FIRE:{ShowPlayerDialog(playerid, DIALOG_DUTY_FD, DIALOG_STYLE_MSGBOX, "Active firefighter duty", "Go off firefighter duty?", "Yes", "No");}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you have another job.");}
				}
			}
			case PICKUP_FACTION_FBI:{
			    switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{ShowPlayerDialog(playerid, DIALOG_DUTY_FBI, DIALOG_STYLE_MSGBOX, "Active FBI duty", "Go on FBI duty as a rookie agent?", "Yes", "No");}
				    case JOB_FBI:{ShowPlayerDialog(playerid, DIALOG_DUTY_FBI, DIALOG_STYLE_MSGBOX, "Active medic duty", "Go off FBI duty?", "Yes", "No");}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you have another job.");}
				}
			}
			case PICKUP_FACTION_DMV:{
			    switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{ShowPlayerDialog(playerid, DIALOG_DUTY_DMV, DIALOG_STYLE_MSGBOX, "Active DMV duty", "Go on DMV duty as a rookie service officer?", "Yes", "No");}
				    case JOB_DMV:{ShowPlayerDialog(playerid, DIALOG_DUTY_DMV, DIALOG_STYLE_MSGBOX, "Active DMV duty", "Go off DMV duty?", "Yes", "No");}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you have another job.");}
				}
			}
			case PICKUP_FACTION_NEWS:{
			    switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{ShowPlayerDialog(playerid, DIALOG_DUTY_NEWS, DIALOG_STYLE_MSGBOX, "Active broadcasting duty", "Go on network duty as a rookie reporter?", "Yes", "No");}
				    case JOB_NEWS:{ShowPlayerDialog(playerid, DIALOG_DUTY_NEWS, DIALOG_STYLE_MSGBOX, "Active broadcasting duty", "Go off network duty?", "Yes", "No");}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you have another job.");}
				}
			}
		}
	
	}

	return 1;
}

public OnPlayerLeaveDynamicArea(playerid, areaid){  // Requires streamer
	// Reactivate pickup after player leaves it
	new area = GetGVarInt("disable_pickups", playerid);
	if(area == 0 && areaid != 0){   // Stop if player had not had this area disabled (With the exception of areaid 0 as GetVarInt() returns 0 on null)
	    return;
	}
	else if(area == 0){ // Player has not had picks disabled
		return;
	}
	if(area == areaid){ // Player had this pickup disabled
		DestroyDynamicArea(GetGVarInt("disable_pickups", playerid));
		DeleteGVar("disable_pickups", playerid);
	}
}

public OnVehicleMod(playerid, vehicleid, componentid)	// TODO for 0.0a
{
	return 1;
}

public OnVehiclePaintjob(playerid, vehicleid, paintjobid)	// TODO for 0.0a
{
	return 1;
}

public OnVehicleRespray(playerid, vehicleid, color1, color2)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerSelectedMenuRow(playerid, row)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerExitedMenu(playerid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerInteriorChange(playerid, newinteriorid, oldinteriorid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)	// TODO for 0.0a
{
	return 1;
}

public OnRconLoginAttempt(ip[], password[], success){
	new message[30 + MAX_SQL_IP + 1];
	if(success){
		format(message, sizeof(message), "* [%s] authenticated via RCON.", ip);
	}
	else{
		format(message, sizeof(message), "* [%s] invalid RCON authentication.", ip);
	}

	logger(LOGLEVEL_NOTICE, message);	// Log event
	DiscordEcho(message, MANAGEMENT_CHANNEL);	// Notify Discord management.

	return 1;
}

public OnPlayerUpdate(playerid)	// Don't use for now.
{
	return 1;
}

public OnPlayerStreamIn(playerid, forplayerid){

	return 1;
}

public OnPlayerStreamOut(playerid, forplayerid)	// TODO for 0.0a
{
	return 1;
}

public OnVehicleStreamIn(vehicleid, forplayerid)	// TODO for 0.0a
{
	return 1;
}

public OnVehicleStreamOut(vehicleid, forplayerid)	// TODO for 0.0a
{
	return 1;
}

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]){
	switch(dialogid){

		case DIALOG_CHANGENAME:{
			if(GetGVarInt("userlevel", playerid) > UNREGISTERED_PLAYER){ // Registered player
			    if(!response){  // User aborted
			        return 0;   // Allow escaping dialogm without forcing to continue
				}
			}
			
			changeName(playerid, inputtext);    // Forced name check and change
		}

		case DIALOG_REGISTER:{
			if(response){ // If they clicked 'Yes' or pressed enter
				// Check password complexity
				new Regex:r = Regex_New("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[0-9!@#\\$%\\^&\\*\\(\\)\\-\\_=+[{\\]}\\\\|;:'\",<.>\\/?]).{8,}$");	// Regex password filter
				new isSecurePassword = Regex_Check(inputtext, r); // Validate name to filter
				Regex_Delete(r);
				if(!isSecurePassword){   // Insecure password
				    ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Insecure password", "Enter a secure password, containg at least:\n\n * A lowercase letter (a-z)\n * A capiral letter (A-Z)\n * A number (0-9).\n * A character that is not a letter (0-9!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?).\n * 8 characters.", "Register", "Cancel");	// Show password requirements.
				}
				else {  // Secure password
					// Hash password
					new buffer[MAX_SQL_HASH + 1], hash[MAX_SQL_HASH + 1];
					WP_Hash(buffer, sizeof(buffer), inputtext);
					GetGVarString("hash", hash, sizeof(hash), playerid);

					if(isempty(hash)){	// First password dialog
						SetGVarString("hash", buffer, playerid);	// Save password has a GVar
						ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password confirmation", "Repeat your password to confirm.", "Register", "Cancel");	// Show password confirmation dialog
					}
					else{	// Password confirmation
						if(!isequal(hash, buffer)){	// Password does not match confirmation
					    	DeleteGVar("hash", playerid);
					    
							SendClientMessage(playerid, COLOR_RED, "ERROR: Password does not match password confirmation.");
							ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password", "Enter the same password twice.", "Continue", "Cancel");	// Password prompt
						}
						else{	// Password matches confirmation
						 	DeleteGVar("hash", playerid);
					 	
							new client_connect_username[MAX_PLAYER_NAME + 1], playerIP[MAX_SQL_IP + 1], escaped_username[MAX_PLAYER_NAME + 1];
							GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);
							GetPlayerIp(playerid, playerIP, sizeof(playerIP));
							sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username));

							// Create user record
							new user_query[61 + MAX_PLAYER_NAME + MAX_SQL_HASH + 1 + MAX_SQL_IP + 1];
							format(user_query, sizeof(user_query), "INSERT INTO \"user\"(name, password) VALUES('%s', '%s')", escaped_username, hash);
							/*new Result:user_result = sql_query(sqlHandle, user_query);
							//new id = sql_insert_id(user_result);   // Broken, always returns 0.
							// sql_insert_id workaround*/
							sql_query(sqlHandle, user_query);
							
							new id = getUserID(playerid);  // Get ID user user record
							
							SetGVarInt("userlevel", REGISTERED_PLAYER, playerid);

							// Create user_ip relation table record
							new user_ip_query[50 + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1 ];
							format(user_ip_query, sizeof(user_ip_query), "INSERT INTO user_ip(id, id) VALUES(%i, %i)", getIPID(playerid), id);
							sql_query(sqlHandle, user_ip_query);

							createCharacterRecord(playerid, id);	// Save character
							
							// Inform user
							new dialogMessage[135 + MAX_PLAYER_NAME + 1], message[31 + MAX_PLAYER_NAME + 1];
							format(dialogMessage, sizeof(dialogMessage), "To login as any of your characters, connect as: \nRemember your username carefully, as you have to use it to connect with SA-MP client!", client_connect_username);
							ShowPlayerDialog(playerid, DIALOG_ACCOUNT_CREATED, DIALOG_STYLE_MSGBOX, "Account created", dialogMessage, "Play", "");
							format(message, sizeof(message), "SERVER: Remember your username, %s!", client_connect_username);
							SendClientMessage(playerid, COLOR_WARNING, message);
							
							// send discord admin echo
							new admin_message[30 + 4 + MAX_PLAYER_NAME + 1];
							format(admin_message, sizeof(admin_message), "[%i] %s has registered an account.", playerid, client_connect_username);
							DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
						}
					}
				}
			}
			else{ // Pressed ESC or clicked cancel
				DeleteGVar("hash", playerid);
			}
		}
		case DIALOG_LOGIN:{
			if(response){ // If they clicked 'Yes' or pressed enter
				// Escape username
				new client_connect_username[MAX_PLAYER_NAME + 1], escaped_username[MAX_PLAYER_NAME + 1];
				GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);
				sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username));

				// Get account
				new user_query[82 + MAX_PLAYER_NAME + 1], hash[MAX_SQL_HASH + 1];
				format(user_query, sizeof(user_query), "SELECT id, password, level FROM \"user\" WHERE name = '%s'", escaped_username);
				new Result:user_result = sql_query(sqlHandle, user_query);
				sql_get_field_assoc_ex(user_result, 0, "password", hash, sizeof(hash));

				// Compare hashes
				new buffer[MAX_SQL_HASH + 1];
				WP_Hash(buffer, sizeof(buffer), inputtext);
				if (!isequal(buffer, hash)){ //	Hashes don't match
					// Brute-force protection
						new authentication_count = GetGVarInt("authentication_count", playerid);
						authentication_count++;
						SetGVarInt("authentication_count", authentication_count, playerid);
						ShowPlayerDialog(playerid, DIALOG_LOGIN_FAILED, DIALOG_STYLE_MSGBOX, "Authenticaion failed", "Invalid password.", "Wait", "");
						switch(authentication_count){
							case 0: SetTimerEx("authenticate", 3000, false, "i", playerid);	// Return to authentication workflow in 3 second.
							case 1: SetTimerEx("authenticate", 5000, false, "i", playerid);	// Return to authentication workflow in 5 seconds.
							case 2: SetTimerEx("authenticate", 7000, false, "i", playerid);	// Return to authentication workflow in 7 seconds.
							default: ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");  // After 4 failed authentication attempts
						}
				}
				else{	// Hashes match
				    // Set userlevel    (This has to be done before banning, else the user ban record won't save)
        			new userlevel = sql_get_field_assoc_int(user_result, "level");
        			printf("Setting userlevel: %i", userlevel);
					SetGVarInt("userlevel", userlevel, playerid);
					
					// Check bans
					new ban_query[76 + MAX_SQL_INTEGER + 1];
					format(ban_query, sizeof(ban_query), "SELECT user_id FROM user_ban WHERE user_id = %i AND expires > NOW()::timestamp", getUserID(playerid));
					new Result:ban_result = sql_query(sqlHandle, ban_query);
					if(sql_num_rows(ban_result) > 0){  // Banned
					    new reason[121 + 1];
						format(reason, sizeof(reason), "Logged in with banned account: %i", sql_get_field_assoc_int(ban_result, "user_id"));
					    banPlayer(playerid, -1, reason, 10);
					}
					
					// Get character names
					characterSelection(playerid);
				}
			}
			else{	// Pressed ESC or clicked cancel
				ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");
			}
		}
		case DIALOG_CHANGE_USERNAME:{
			if(response){ // If they clicked 'Yes' or pressed enter
				new Regex:r = Regex_New("^[0-9a-zA-Z\\[\\]\\(\\)\\$@._=]{1,}$");	// Regex name filter
				new isValidName = Regex_Check(inputtext, r);
				Regex_Delete(r);
				if(isValidName){	// Valid name
				    // Get user record
					new escaped_username[MAX_PLAYER_NAME + 1], user_query[36 + MAX_PLAYER_NAME + 1];
					sql_escape_string(sqlHandle, inputtext, escaped_username, sizeof(escaped_username));	// Escape player name
					format(user_query, sizeof(user_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username);
					//new Result:result = sqlQuery(sqlHandle, user_query);  // Middleware broken.
					new Result:result = sql_query(sqlHandle, user_query);
					
					if(sql_num_rows(result) > 0){  // Username taken
					    ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");
					}
					else{   // Username free
					    SetGVarString("client_connect_username", inputtext, playerid); // Used by register and DIALOG_LOGIN
					    SetPlayerName(playerid, inputtext);	// Change name in-game

						return 1;   // Do nothing to let player continue class selection
					}
				}
				else{	// Invalid name
					ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Invalid name. Names may only contain numbers (0-9), letters (a-z) & (A-Z) and special characters ([]()$@._=)", "Change", "");
				}
			}
			else{	// Pick another username
			    ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");
			}
		}
		case DIALOG_CHARACTERS:{    // TODO set color & skin in case of job
			if(response){ // Spawn as character
			    new character_array_string[MAX_CHARACTERS_PER_USER * MAX_SQL_INTEGER + 1], character_array[MAX_CHARACTERS_PER_USER + 1];
       			GetGVarString("character_array_string", character_array_string, sizeof(character_array_string), playerid);
       			DeleteGVar("character_array_string");
       			
       			strtobin(character_array, character_array_string);
			    
				new character_query[124 + MAX_PLAYER_NAME + 1], name[24 + 1];
				format(character_query, sizeof(character_query), "SELECT name, skin_id, cash, armour, health, pos_x, pos_y, pos_z, rotation, world_id, interior_id FROM character WHERE id = %i", character_array[listitem]);
				new Result:character_result = sql_query(sqlHandle, character_query);
				sql_get_field_assoc(character_result, "name", name, sizeof(name));
				new skin_id = sql_get_field_assoc_int(character_result, "skin_id");
				new cash = sql_get_field_assoc_int(character_result, "cash");
				new Float:armour = sql_get_field_assoc_int(character_result, "armour");
				new Float:health = sql_get_field_assoc_int(character_result, "health");
				new Float:pos_x = sql_get_field_assoc_int(character_result, "pos_x");
				new Float:pos_y = sql_get_field_assoc_int(character_result, "pos_y");
				new Float:pos_z = sql_get_field_assoc_int(character_result, "pos_z");
				new Float:rotation = sql_get_field_assoc_int(character_result, "rotation");
				new world_id = sql_get_field_assoc_int(character_result, "world_id");
				new interior_id = sql_get_field_assoc_int(character_result, "interior_id");
				
				// Temporary bug workaround TODO find cause
				if(pos_x == 0 && pos_y == 0 && pos_z == 0){ // Sometimes this happens. Due to unreliable OnPlayerDisconnect() & OnGamemodeExit(), due to character creation, or due to (forgot the other possiblity, but had a strong hunch)?
				    pos_x = -144.0328;
					pos_y = 1225.0564;
					pos_z = 19.8992;
					rotation = 175.5507;
				}
				SetSpawnInfo(playerid, 0, skin_id, pos_x, pos_y, pos_z, rotation, 0, 0, 0, 0, 0, 0);
                SetGVarInt("character_id", character_array[listitem], playerid);
                
                SetPlayerName(playerid, name);	// Change name in-game
                SetPlayerColor(playerid, COLOR_WHITE);
                SpawnPlayer(playerid);
                ResetPlayerMoney(playerid);
                GivePlayerMoney(playerid, cash);
                SetPlayerHealth(playerid, health);
                SetPlayerArmour(playerid, armour);
                SetPlayerVirtualWorld(playerid, world_id);
				SetPlayerInterior(playerid, interior_id);
                
                // Notify all players
				new message[16 + 4 + MAX_PLAYER_NAME + 1];
				format(message, sizeof(message), "* [%i] %s joined.", playerid, getCharacterName(playerid));
				SendClientMessageToAll(COLOR_NOTICE, message);	// Notify all players.
				DiscordEcho(message, ECHO_CHANNEL);	// Notify discord public echo
                
			}
			else{	// New character
			    // character cap
			    new id = getUserID(playerid);
				new character_query[60 + MAX_SQL_INTEGER + 1];
				format(character_query, sizeof(character_query), "SELECT id FROM character WHERE character_id = %i", id);
				new Result:character_result = sql_query(sqlHandle, character_query);
				printf("%i", sql_num_rows(character_result));
				if(sql_num_rows(character_result) >= MAX_CHARACTERS_PER_USER){  // At or over character limit
				    SendClientMessage(playerid, COLOR_WARNING, "SERVER: Maximum characters reached, can not create another.");
				    characterSelection(playerid);
				}
			    else{
			        ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Pick a name.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", "");	// Force RP name.
			        // changeName(playerid, "");	// Pick name and save character ANNOYING this forces new character creation, no way out.
				}
                
			}
		}
        case DIALOG_LOGIN_FAILED:{
			ShowPlayerDialog(playerid, DIALOG_LOGIN_FAILED, DIALOG_STYLE_MSGBOX, "Authenticaion failed", "Take some time to think about your password...", "Wait", "Longer");
		}
		case DIALOG_DELETE_CHARACTER:{
			if(response){ // Delete character
				new playername[MAX_PLAYER_NAME + 1];
				playername = getPlayerName(playerid);
				
				// Checkcharacter amount
				new id = getUserID(playerid), character_query[75 + MAX_SQL_INTEGER + MAX_CHARACTERS_PER_USER_DIGITS + 1];
				format(character_query, sizeof(character_query), "SELECT id, name FROM character WHERE user_id = %i LIMIT %i", id, MAX_CHARACTERS_PER_USER);
				new Result:character_result = sql_query(sqlHandle, character_query);
                if(sql_num_rows(character_result) < 2){  // 1 character or less
                    SendClientMessage(playerid, COLOR_WARNING, "SERVER: You can not have less then one character.\nCreate another character,before deleting this one.");
                
				}
			    else{   // More then 1 character
					// delete character record
					new delete_query[50 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
					format(delete_query, sizeof(delete_query), "DELETE FROM character WHERE name = '%s' AND user_id = %i", playername, getUserID(playerid));
					sql_query(sqlHandle, delete_query);
			
					// infordm admin echo
					new client_connect_username[MAX_PLAYER_NAME + 1], message[28 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
					GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);   // Get client connect name
					format(message, sizeof(message), "* [%i] %s has been deleted by it's user: %s", playerid, getCharacterName(playerid), client_connect_username);
					DiscordEcho(message, ADMIN_ECHO_CHANNEL);
					sendToAdmins(COLOR_COMMAND_OUTPUT, message);
					
					// Player must not be spawned or will be able to esacpe the character menu and continue with character without dartabase record
					ForceClassSelection(playerid);
					TogglePlayerSpectating(playerid, true);
				    TogglePlayerSpectating(playerid, false);
				    
					characterSelection(playerid);
				}
			}
		}
		case DIALOG_GOTO:{
		    if(response){
				switch(listitem){
				    case GOTO_CATEGORY_INTERIOR:{
				        dialogGotoInterior(playerid);
					}
					/*case GOTO_CATEGORY_PLAYER:{
				        dialogGotoPlayer(playerid);
					}*/
				}
			}
		}
        case DIALOG_GOTO_INTERIOR:{
		    if(response){
				switch(listitem){
				    case INTERIOR_CATEGORY_247:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_247, DIALOG_STYLE_TABLIST, "24/7's", "L-shaped\tBig\tNO EXIT\nOblong\tBig\tNO EXIT\nSquare\tMedium\tCreek, LV\nSquare\tMedium\tNO EXIT\nLong\tSmall\tMulholland\nSquare\tSmall\tWhetstone", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_AVIATION:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_AVIATION, DIALOG_STYLE_TABLIST, "Aviation interiors", "Francis Intn'l Airport - Ticket sales\tStarting Cutscene\nFrancis Intn'l Airport - Baggage claim\tStarting Cutscene\nShamal cabin\tMission \"Free Fall\"\nAndromada cargo hold\tMission \"Stowaway\"\nLS Airport, Baggage Reclaim\tCutscene in \"Opening Mission\"", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_AMMUNATION:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_AMMUNATION, DIALOG_STYLE_TABLIST, "Ammunation's", "Ocean Flats\tSF\nPalomino Creek\tLV\nAngel Pine\tSF\nEl Quebrados\tLV\n2 Stories/t with Booth and Range\n Booth\n Range", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_BUGRLARY:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_BURGLARY, DIALOG_STYLE_TABLIST, "Burglary houses", "Large\t2 story\t3 bedroom clone of house 9\t\nMedium\t1 story\t1 bedroom\tEast LS\nSmall\t1 story\t1 bedroom\tCalton Heights, SF\nVery large\t2 story\t4 bedrooms\nSmall\t1 story\t2 bedrooms\tJefferson LS\nSmall\t1 story\t2 bedrooms\tEast LS\nSmall\t1 story\t 1 bedroom (NO BATHROOM!)", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_MISSION:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_MISSION, DIALOG_STYLE_TABLIST, "Mission interiors", "Atrium\tMission \"Just Business\"\tLS\nBig Smoke's Crack Palace\tMission \"End of the Line\"\nJefferson Motel\tMission \"Reuniting the Families\"\tLS\nPleasure Domes/Jizzy's\tMission \"Ice Cold Killa\"\tSF\nRC Battlefield\tMission \"New Model Army\"\nWoozies Apartment/Wu-Zi Mu's\t\tSF\n", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_MISSIONEXT:{
					    ShowPlayerDialog(playerid, DIALOG_GOTO_MISSIONEXT, DIALOG_STYLE_TABLIST, "Mission exteriors", "Dillimore Gas Station\tMission \"Tanker Commander\"\nLiberty City\tMission \"Saint Marks Bistro\"\nSan Fierro Garage\tDoherty (Locked Camera Position}", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_MISSIONHOUSE:{
                        ShowPlayerDialog(playerid, DIALOG_GOTO_MISSIONHOUSE, DIALOG_STYLE_TABLIST, "Mission houses", "Burning Desire house\t\tEast LS\nColonel Furhberger's\tMission \"Home Invasion\"\t Los Flores, LS\nRyder's house\nSweet's house\tCutscene in \"First Date\"\nCrack Den\tMission \"Cleaning The Hood\"", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_MODSHOP:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_MODDING, DIALOG_STYLE_TABLIST, "Modding shops", "Loco Low Co\tLos Santos\nWheel Arch Angels\tSan Fierro\nTransfender", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_POLICE:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_POLICE, DIALOG_STYLE_LIST, "Police departments", "Las Venturas\nLos Santos\nSan Fierro\nBarbara's love nest", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_HOUSES:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_HOUSE, DIALOG_STYLE_TABLIST, "Safe houses", "Golden Bed motel room\t\t\nHashbury house\tHashburry\tSF\nThe Johnson house\tGanton\tLS\nMadd Doggs mansion\tMulholland\tLS\nRed Bed motel Room\nVerdant Bluffs safehouse\tVerdant Bluffs\t LV\nUnused safehouse\tLarge modern safehouse", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_SHOPS:{
						ShowPlayerDialog(playerid, DIALOG_GOTO_SHOP, DIALOG_STYLE_TABLIST, "Shops & casino's", "Tattoos\tIdlewood\tLS\nBurger Shot\tWhitewood Estates\nWell Stacked Pizza\tBlueberry\nCluckin' Bell\nCaligulas casino\tThe Strip\tLV\nCasino (Redsands West)\tThe Strip\tLV\n4 Dragons casino\tThe Strip\tLV\nRusty Brown's donuts\tMission \"Burning Desire\"\nZero's RC shop\tGarcia\tSF\nThe Welcome Pump", "Goto", "Cancel");
					}
					case INTERIOR_CATEGORY_STADIA:{
					    ShowPlayerDialog(playerid, DIALOG_GOTO_STADIA, DIALOG_STYLE_TABLIST, "Stadia", "Bloodbowl stadium\nKickstart stadium\n8 Track stadium\nDirtbike stadium\n", "Goto", "Cancel");
					}
				}
        	}
			else{
			    ShowPlayerDialog(playerid, DIALOG_GOTO, DIALOG_STYLE_LIST, "Categories", "Interiors", "Select", "Cancel");
			}
		}
		case DIALOG_GOTO_247:{
		    if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[25 + 1];
				switch(listitem){
					case INTERIOR_247_LSHAPED:{
						x = -25.7220;
						y = -187.8216;
						z = 1003.5469;
						world = GetPlayerVirtualWorld(playerid);
						interior = 17;
						angle = 5.0760;
						name = "L-shaped ammunation";
					}
					case INTERIOR_247_OBLONG:{
						x = 6.0856;
						y = -28.8966;
						z = 1003.5494;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 5.0365;
	                    name = "Oblong ammunation";
					}
					case INTERIOR_247_MEDIUM:{
						x = -30.9875;
						y = -89.6806;
						z = 1003.5469;
						world = GetPlayerVirtualWorld(playerid);
						interior = 18;
						angle = 359.8401;
	                    name = "Medium ammunation";
					}
					case INTERIOR_247_MEDIUM_NOEXIT:{
						x = -26.1856;
						y = -140.9164;
						z = 1003.5469;
						world = GetPlayerVirtualWorld(playerid);
						interior = 16;
						angle = 2.9087;
					    name = "Medium no exit ammunation";
					}
					case INTERIOR_247_LONG:{
						x = -27.844;
						y = 26.6737;
						z = 1003.5573;
						world = GetPlayerVirtualWorld(playerid);
						interior = 4;
						angle = 184.3118;
					    name = "Small long ammunation";
					}
					case INTERIOR_247_SQAURE:{
						x = -26.8339,
						y = -55.5846,
						z = 1003.5469,
						world = GetPlayerVirtualWorld(playerid),
						interior = 6,
						angle = 3.9528;
						name = "Small square ammunation";
					}
				}
				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_AVIATION:{
		    if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[39 + 1];
				switch(listitem){
					case INTERIOR_AVIATION_TICKET:{
						x = -1827.147338;
						y = 7.207418;
						z = 1061.143554;
						world = GetPlayerVirtualWorld(playerid);
						interior = 14;
						angle = 335.3199;
						name = "Francis Intn'l Airport - Ticket sales";
					}
					case INTERIOR_AVIATION_BAGGAGE:{
						x = -1855.568725;
						y = 41.263156;
						z = 1061.143554;
						world = GetPlayerVirtualWorld(playerid);
						interior = 14;
						angle = 335.3199;
	                    name = "Francis Intn'l Airport - Baggage claim";
					}
					case INTERIOR_AVIATION_SHAMAL:{
						x = 2.384830;
						y = 33.103397;
						z = 1199.849976 ;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 359.8401;
	                    name = "Shamal cabin";
					}
					case INTERIOR_AVIATION_ANDROMADA:{
						x = 315.856170;
						y = 1024.496459;
						z = 1949.797363;
						world = GetPlayerVirtualWorld(playerid);
						interior = 9;
						angle = 359.6368;
					    name = "Andromada cargo hold";
					}
					case INTERIOR_AVIATION_LSAIRPORT:{
						x = -1870.80;
						y = 59.81;
						z = 1056.25;
						world = GetPlayerVirtualWorld(playerid);
						interior = 14;
						angle = 85.8541;
						name = "LS Airport, Baggage Reclaim";
					}
				}

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_AMMUNATION:{   // TODO there are more ammunations that deserve a booth and range teleport
		    if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[31 + 1];
				switch(listitem){
					case INTERIOR_AMMU_OCEAN:{
						x = 286.148987;
						y = -40.644398 ;
						z = 1001.569946;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 0.7529;
						name = "Ocean Flats, SF";
					}
					case INTERIOR_AMMU_PALOMINO:{
						x = 286.800995;
						y = -82.547600;
						z = 1001.539978;
						world = GetPlayerVirtualWorld(playerid);
						interior = 4;
						angle = 290.2195;
	                    name = "Palomino Creek, LV";
					}
					case INTERIOR_AMMU_ANGEL:{
						x = 296.919983;
						y = -108.071999;
						z = 1001.569946;
						world = GetPlayerVirtualWorld(playerid);
						interior = 6;
						angle = 290.2195;
	                    name = "Angel Pine, SF";
					}
					case INTERIOR_AMMU_QUEBRADOS:{
						x = 316.524994;
						y = -167.706985;
						z = 999.661987;
						world = GetPlayerVirtualWorld(playerid);
						interior = 6;
						angle = 10.3031;
	                    name = "El Quebrados, LV";
					}
					case INTERIOR_AMMU_2STORIES:{
						x = 314.820984;
						y = -141.431992;
						z = 999.661987;
						world = GetPlayerVirtualWorld(playerid);
						interior = 7;
						angle = 20.2254;
	                    name = "2 Stories, with booth and range";
					}
					case INTERIOR_AMMU_BOOTH:{
						x = 302.292877;
						y = -143.139099;
						z = 1004.062500;
						world = GetPlayerVirtualWorld(playerid);
						interior = 7;
						angle = 20.2254;
	                    name = "Booth";
					}
					case INTERIOR_AMMU_RANGE:{
						x = 280.795104;
						y = -135.203353;
						z = 1004.062500;
						world = GetPlayerVirtualWorld(playerid);
						interior = 7;
						angle = 20.2254;
	                    name = "Range";
					}
				}

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
        case DIALOG_GOTO_BURGLARY:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[45 + 1];
				switch(listitem){
					case INTERIOR_BURGRLARY_LARGE:{
						x = 235.508994;
						y = 1189.169897;
						z = 1080.339966;
						world = GetPlayerVirtualWorld(playerid);
						interior = 3;
						angle = 349.4844;
						name = "Large: 2 story, 3 bedroom clone of house 9";
					}
					case INTERIOR_BURGRLARY_MEDIUM:{
						x = 225.756989;
						y = 1240.000000;
						z = 1082.149902;
						world = GetPlayerVirtualWorld(playerid);
						interior = 2;
						angle = 96.2852;
						name = "Medium: 1 story, 1 bedroom";
					}
					case INTERIOR_BURGRLARY_SMALL:{
						x = 223.043991;
						y = 1289.259888;
						z = 1082.199951;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 359.868;
						name = "Small: 1 story, 1 bedroom";
					}
                    case INTERIOR_BURGRLARY_VERYLARGE:{
						x = 225.630997;
						y = 1022.479980;
						z = 1084.069946;
						world = GetPlayerVirtualWorld(playerid);
						interior = 7;
						angle = 270.2654;
						name = "VERY Large: 2 story, 4 bedrooms";
					}
					case INTERIOR_BURGRLARY_5:{
						x = 295.138977;
						y = 1474.469971;
						z = 1080.519897;
						world = GetPlayerVirtualWorld(playerid);
						interior = 15;
						angle = 0;
						name = "Small: 1 story, 2 bedrooms";
					}
					case INTERIOR_BURGRLARY_6:{
						x = 328.493988;
						y = 1480.589966;
						z = 1084.449951;
						world = GetPlayerVirtualWorld(playerid);
						interior = 15;
						angle = 0;
						name = "Small: 1 story, 2 bedrooms";
					}
					case INTERIOR_BURGRLARY_NOBATH:{
						x = 385.803986;
						y = 1471.769897;
						z = 1080.209961;
						world = GetPlayerVirtualWorld(playerid);
						interior = 15;
						angle = 0;
						name = "Small: 1 story, 1 bedroom (NO BATHROOM!)";
					}
				}

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_MISSION:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[30 + 1];
				switch(listitem){
					case INTERIOR_MISSION_ATRIUM:{
						x = 1726.18;
						y = -1641.00;
						z = 20.23;
						world = GetPlayerVirtualWorld(playerid);
						interior = 18;
						angle = 172.4193;
						name = "Atrium";
					}
					case INTERIOR_MISSION_BIGSMOKE:{
						x = 2567.52;
						y = -1294.59;
						z = 1063.25 ;
						world = GetPlayerVirtualWorld(playerid);
						interior = 2;
						angle = 254.9548;
						name = "Big Smoke's Crack Palace";
					}
					case INTERIOR_MISSION_JEFFERSON:{
						x = 2220.26;
						y = -1148.01;
						z = 1025.80;
						world = GetPlayerVirtualWorld(playerid);
						interior = 15;
						angle = 273.7328;
						name = "Jefferson Motel";
					}
					case INTERIOR_MISSION_JIZZY:{
						x = -2637.69;
						y = 1404.24;
						z = 906.46;
						world = GetPlayerVirtualWorld(playerid);
						interior = 3;
						angle = 94.6794;
						name = "Pleasure Domes/Jizzy's";
					}
					case INTERIOR_MISSION_RC:{
						x = -1079.99;
						y = 1061.58;
						z = 1343.04;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 274.5268;
						name = "RC Battlefield";
					}
					case INTERIOR_MISSION_WUZI:{
						x = -2158.72;
						y = 641.29;
						z = 1052.38;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 86.5402;
						name = "Woozies Apartment/Wu-Zi Mu's";
					}
				}

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_MISSIONEXT:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[30 + 1];
				switch(listitem){
		            case INTERIOR_MISSIONEXT_GAS:{
						x = 664.19;
						y = -570.73;
						z = 16.34;
						world = GetPlayerVirtualWorld(playerid);
						interior = 0;
						angle = 264.9829;
						name = "Dillimore Gas Station";
					}
					case INTERIOR_MISSIONEXT_LIBERTY:{ // TODO Create extra interior inside the shop
						x = -750.80;
						y = 491.00;
						z = 1371.70;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 71.7782;
						name = "Liberty City";
					}
					case INTERIOR_MISSIONEXT_SFGARAGE:{    // TODO Create extra interior of area that does not capture the camera & see about the camera camera capturing (Use it for broadcast? & In other world or interior place more usable?)
						x = -2042.42;
						y = 178.59;
						z = 28.84;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 156.2153;
						name = "San Fierro Garage Interior";
					}
                }

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_MISSIONHOUSE:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[30 + 1];
				switch(listitem){
		            case INTERIOR_MISSIONHOUSE_DESIRE:{
						x = 2338.32;
						y = -1180.61;
						z = 1027.98;
						world = GetPlayerVirtualWorld(playerid);
						interior = 5;
						angle = 99.1864;
						name = "Burning Desire House";
					}
					case INTERIOR_MISSIONHOUSE_COLONEL:{
						x = 2807.63;
						y = -1170.15;
						z = 1025.57;
						world = GetPlayerVirtualWorld(playerid);
						interior = 8;
						angle = 193.7117;
						name = "Colonel Furhberger's";
					}
					case INTERIOR_MISSIONHOUSE_RYDER:{
						x = 2451.77;
						y = -1699.80;
						z = 1013.51;
						world = GetPlayerVirtualWorld(playerid);
						interior = 2;
						angle = 314.5253;
						name = "Ryder's House";
					}
					case INTERIOR_MISSIONHOUSE_SWEET:{
						x = 2535.83;
						y = -1674.32;
						z = 1015.50;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 260.9709;
						name = "Sweet's House";
					}
					case INTERIOR_MISSIONHOUSE_CRACK:{
						x = 318.565;
						y = 1115.210;
						z = 1082.98;
						world = GetPlayerVirtualWorld(playerid);
						interior = 5;
						angle = 267.459;
						name = "Crack den";
					}
				}

                new target_player = GetGVarInt("goto_target", playerid);
                DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_MODDING:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[25 + 1];
				switch(listitem){
					case INTERIOR_MOD_LOCO:{
						x = 616.7820;
						y = -74.8151;
						z = 997.6350;
						world = GetPlayerVirtualWorld(playerid);
						interior = 2;
						angle = 320.9263;
						name = "Loco Low Co";
					}
					case INTERIOR_MOD_WHEEL:{
						x = 615.2851;
						y = -124.2390;
						z = 997.6350;
						world = GetPlayerVirtualWorld(playerid);
						interior = 3;
						angle = 266.5704;
						name = "Wheel Arch Angels";
					}
					case INTERIOR_MOD_TRANSFENDER:{
						x = 617.5380;
						y = -1.9900;
						z = 1000.6829;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 15.6789;
						name = "Transfender";
					}
				}
				
				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_POLICE:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[30 + 1];
				switch(listitem){
					case INTERIOR_PD_LV:{
						x = 288.4723;
						y = 170.0647;
						z = 1007.1794;
						world = GetPlayerVirtualWorld(playerid);
						interior = 3;
						angle = 22.0477;
						name = "Las Venturas police deparment";
					}
					case INTERIOR_PD_LS:{
						x = 246.6695;
						y = 65.8039;
						z = 1003.6406;
						world = GetPlayerVirtualWorld(playerid);
						interior = 6;
						angle = 7.9562;
						name = "Los Santos police deparment";
					}
					case INTERIOR_PD_SF:{
						x = 246.0688;
						y = 108.9703;
						z = 1003.2188;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 0.2922;
						name = "San Fierro police deparment";
					}
					case INTERIOR_PD_BARBARA:{
						x = 322.5014;
						y = 303.6906;
						z = 999.1484;
						world = GetPlayerVirtualWorld(playerid);
						interior = 5;
						angle = 8.1747;
						name = "Barbara's Love nest";
					}
				}
				
				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_HOUSE:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[25 + 1];
				switch(listitem){
					case INTERIOR_HOUSE_GOLDEN:{
						x = 2251.85;
						y = -1138.16;
						z = 1050.63;
						world = GetPlayerVirtualWorld(playerid);
						interior = 9;
						angle = 167.3959;
						name = "Golden Bed motel room";
					}
					case INTERIOR_HOUSE_HASHBURY:{
						x = 2260.76;
						y = -1210.45;
						z = 1049.02;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 266.88;
						name = "Hashbury house";
					}
					case INTERIOR_HOUSE_JOHNSON:{
						x = 2496.65;
						y = -1696.55;
						z = 1014.74;
						world = GetPlayerVirtualWorld(playerid);
						interior = 3;
						angle = 179.2174;
						name = "The Johnson house";
					}
					case INTERIOR_HOUSE_MADDDOGG:{
						x = 1299.14;
						y = -794.77;
						z = 1084.00;
						world = GetPlayerVirtualWorld(playerid);
						interior = 5;
						angle = 231.3418;
						name = "Madd Doggs mansion";
					}
                    case INTERIOR_HOUSE_RED:{
						x = 2262.83;
						y = -1137.71;
						z = 1050.63;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 266.88;
						name = "Red Bed motel room";
					}
                    case INTERIOR_HOUSE_VERDANT:{
						x = 2365.42;
						y = -1131.85;
						z = 1050.88;
						world = GetPlayerVirtualWorld(playerid);
						interior = 8;
						angle = 177.3947;
						name = "Verdant Bluffs safehouse";
					}
					case INTERIOR_HOUSE_UNUSED:{
						x = 2324.33;
						y = -1144.79;
						z = 1050.71;
						world = GetPlayerVirtualWorld(playerid);
						interior = 12;
						angle = 269.0954;
						name = "Unused safe house";
					}
				}

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_SHOP:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[25 + 1];
				switch(listitem){
					case INTERIOR_SHOP_TATTOO:{
						x = -203.0764;
						y = -24.1658;
						z = 1002.2734;
						world = GetPlayerVirtualWorld(playerid);
						interior = 16;
						angle = 252.8154;
						name = "Tattoos";
					}
					case INTERIOR_SHOP_BURGER:{
						x = 366.0248;
						y = -73.3478;
						z = 1001.5078;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 292.0084;
						name = "Burger Shot";
					}
                    case INTERIOR_SHOP_PIZZA:{
						x = 372.3520;
						y = -131.6510;
						z = 1001.4922;
						world = GetPlayerVirtualWorld(playerid);
						interior = 5;
						angle = 354.2285;
						name = "Well Stacked Pizza";
					}
                    case INTERIOR_SHOP_CLUCKIN:{
						x = 365.7158;
						y = -9.8873;
						z = 1001.8516;
						world = GetPlayerVirtualWorld(playerid);
						interior = 9;
						angle = 160.528;
						name = "Cluckin' Bell";
					}
                    case INTERIOR_SHOP_CALIGULAS:{
						x = 2233.8032;
						y = 1712.2303;
						z = 1011.7632;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 184.3891;
						name = "Caligulas Casino";
					}
                    case INTERIOR_SHOP_CASINO:{
						x = 1118.8878;
						y = -10.2737;
						z = 1002.0859;
						world = GetPlayerVirtualWorld(playerid);
						interior = 12;
						angle = 165.8482;
						name = "Casino (Redsands West)";
					}
                    case INTERIOR_SHOP_4DRAGONS:{
						x = 2016.2699;
						y = 1017.7790;
						z = 996.8750;
						world = GetPlayerVirtualWorld(playerid);
						interior = 10;
						angle = 88.0055;
						name = "4 Dragons Casino";
					}
                    case INTERIOR_SHOP_DONUTS:{
      					x = 376.99;
						y = -191.21;
						z = 1000.63;
						world = GetPlayerVirtualWorld(playerid);
						interior = 17;
						angle = 141.0245;
						name = "Rusty Brown's donuts";
					}
					case INTERIOR_SHOP_RC:{
						x = -2240.00;
						y = 131.00;
						z = 1035.40;
						world = GetPlayerVirtualWorld(playerid);
						interior = 6;
						angle = 269.0954;
						name = "Zero's RC shop";
					}
					case INTERIOR_SHOP_PUMP:{
						x = 681.66;
						y = -453.32;
						z = -25.61;
						world = GetPlayerVirtualWorld(playerid);
						interior = 1;
						angle = 166.166;
						name = "The Welcome Pump";
					}
				}
				
				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
		case DIALOG_GOTO_STADIA:{
			if(response){
		        new Float:x, Float:y, Float:z, world, interior, Float:angle, name[25 + 1];
				switch(listitem){
				    case INTERIOR_STADIUM_BLOODBOWL:{
						x = -1394.20;
						y = 987.62;
						z = 1023.96;
						world = GetPlayerVirtualWorld(playerid);
						interior = 15;
						angle = 0.7013;
						name = "Bloodbowl Stadium";
					}
					case INTERIOR_STADIUM_KICKSTART:{
						x = -1410.72;
						y = 1591.16;
						z = 1052.53;
						world = GetPlayerVirtualWorld(playerid);
						interior = 14;
						angle = 159.1255;
						name = "Kickstart Stadium";
					}
					case INTERIOR_STADIUM_8TRACK:{
						x = -1395.958;
						y = -208.197;
						z = 1051.170;
						world = GetPlayerVirtualWorld(playerid);
						interior = 7;
						angle = 355.8576;
						name = "8 Track Stadium";
					}
					case INTERIOR_STADIUM_DIRTBIKE:{
						x = -1424.9319;
						y = -664.5869;
						z = 1059.8585;
						world = GetPlayerVirtualWorld(playerid);
						interior = 4;
						angle = 170.9341;
						name = "Dirtbike Stadium";
					}
				}

				new target_player = GetGVarInt("goto_target", playerid);
				DeleteGVar("goto_target", playerid);
				teleportPlayer(target_player, x, y, z, world, interior, angle, name);
			}
			else{dialogGotoInterior(playerid);}
		}
				
		case DIALOG_CREATE_PICKUP:{
			if(response){
                switch(listitem){
                    case 0: ShowPlayerDialog(playerid, DIALOG_CREATE_FACTION_PICKUP, DIALOG_STYLE_LIST, "Faction types", "Police\nMedic\nFire fighter\nFederal Bureau of Investigation\nDepeartment of motorvehicles\nBroadcasting", "Select", "Cancel");				}
			}
		}
		case DIALOG_CREATE_FACTION_PICKUP:{
			if(response){
			    printf("Listitem: % i", listitem);
			    new Float:x, Float:y, Float:z;
				GetPlayerPos(playerid, x, y, z);
				new world = GetPlayerVirtualWorld(playerid);
				new interior = GetPlayerInterior(playerid);
				
				new pickupid = createDynamicPickup(1247, x, y, z, world, interior, listitem);

				// Create pickup record
				new pickup_query[127 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1];
				format(pickup_query, sizeof(pickup_query), "INSERT INTO pickup(object_id, pos_x, pos_y, pos_z, world_id, interior_id, type_id, pickup_id) VALUES (1247, '%f', '%f', '%f', %i, %i, %i, %i)", x, y, z, world, interior, listitem, pickupid);
				sql_query(sqlHandle, pickup_query);

				// Get pickup record ID
				new id_query[44 + 1];
				format(id_query, sizeof(id_query), "SELECT id FROM pickup WHERE pickup_id = %i", pickupid);
				new Result:id_result = sql_query(sqlHandle, id_query);
				new pickup_id = sql_get_field_assoc_int(id_result, "id");
						
				new admin_message[21 + 4 + MAX_PLAYER_NAME + MAX_SQL_INTEGER + 1];
				
				// Inform admins
				listitem++;  // To account for FACTION_NONE
				printf("Listitem: % i", listitem);
                switch(listitem){
                    case FACTION_POLICE: format(admin_message, sizeof(admin_message), "* [%i] %s created police faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
					case FACTION_MEDIC: format(admin_message, sizeof(admin_message), "* [%i] %s created medic faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
					case FACTION_FIRE: format(admin_message, sizeof(admin_message), "* [%i] %s created firefighter faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
					case FACTION_SHERIFF: format(admin_message, sizeof(admin_message), "* [%i] %s created shariff faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
					case FACTION_FBI: format(admin_message, sizeof(admin_message), "* [%i] %s created FBI faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
    				case FACTION_DMV: format(admin_message, sizeof(admin_message), "* [%i] %s created DMV faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
    				case FACTION_NEWS: format(admin_message, sizeof(admin_message), "* [%i] %s created broadcasting faction pickup: %i", playerid, getPlayerName(playerid), pickup_id);
					default: return 0;
				}
				sendToAdmins(COLOR_NOTICE, admin_message);
				DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
			}
		}
		
		case DIALOG_DUTY_PD:{       // TODO: Make DRY DUTY_ & _SKIN
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{
						
				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_POLICE_SKIN, DIALOG_STYLE_TABLIST, "Police skins", "Police Officer\tMale\tLS\nPolice Officer\tMale\tSF\nMotorbike Cop\tMale\tLS\nPolice Officer (Without gun holster)\tMale\tLS\nPolice Officer (Without gun holster)\tMale\tSF\nPolice Officer\tFemale\tLS\nPolice Officer\tFemale\tSF", "Duty", "Cancel");
					}
				    case JOB_POLICE:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");
				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);
				        ResetPlayerWeapons(playerid);
				        // TODO: get weaponskill fromddatabase and set it to player.
					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_POLICE_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 280;}
					case 1:{skin = 281;}
					case 2:{skin = 284;}
					case 3:{skin = 300;}
					case 4:{skin = 301;}
					case 5:{skin = 306;}
					case 6:{skin = 307;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_POLICE, playerid);
		        SetPlayerColor(playerid, COLOR_POLICE);
		        SetPlayerSkillLevel(playerid, WEAPONSKILL_PISTOL, 1);
				GivePlayerWeapon(playerid, 3, 1);   	// Baton
				GivePlayerWeapon(playerid, 17, 10);		// Teargas
				GivePlayerWeapon(playerid, 22, 170);    // 10 clips and a 9mm
			}
		}
		case DIALOG_DUTY_MD:{
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{

				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_MEDIC_SKIN, DIALOG_STYLE_TABLIST, "Paramedic skins", "Los Santos white\tMale\nLas Venturas blue\tMale\nSan Fierro green\tMale\nSan Fierro green\tFemale", "Duty", "Cancel");
					}
				    case JOB_MEDIC:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");

				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);
					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_MEDIC_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 274;}
					case 1:{skin = 275;}
					case 2:{skin = 276;}
					case 3:{skin = 308;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_MEDIC, playerid);
		        SetPlayerColor(playerid, COLOR_MEDIC);

			}
		}
		case DIALOG_DUTY_FD:{
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{

				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_FIRE_SKIN, DIALOG_STYLE_TABLIST, "Firefighter skins", "Los Santos green\tMale\nLas Venturas yellow\tMale\nSan Fierro black\tMale", "Duty", "Cancel");
					}
				    case JOB_FIRE:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");

				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);

				        ResetPlayerWeapons(playerid);
					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_FIRE_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 277;}
					case 1:{skin = 278;}
					case 2:{skin = 279;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_FIRE, playerid);
		        SetPlayerColor(playerid, COLOR_FIRE);
		        GivePlayerWeapon(playerid, 42, 1);   	// Fire extinguisher
			}
		}
		case DIALOG_DUTY_SD:{
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{

				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_SHERIFF_SKIN, DIALOG_STYLE_TABLIST, "Sheriff skins", "Officer\tMale\tLV\nCounty sheriff\tMale\tCountryside\nDesert sheriff\tMale\tDesert\nOfficer (Without gun holster)\tMale\nOfficer\tFemale\nCounty sheriff (Without hat)\tMale\nDesert sheriff\tMale", "Duty", "Cancel");
					}
				    case JOB_SHERIFF:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");

				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);
						// TODO restore weaponskill
				        ResetPlayerWeapons(playerid);
					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_SHERIFF_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 282;}
					case 1:{skin = 283;}
					case 2:{skin = 288;}
					case 3:{skin = 302;}
					case 4:{skin = 309;}
					case 5:{skin = 310;}
					case 6:{skin = 311;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_SHERIFF, playerid);
		        SetPlayerColor(playerid, COLOR_SHERIFF);
		        SetPlayerSkillLevel(playerid, WEAPONSKILL_PISTOL, 1);
				GivePlayerWeapon(playerid, 3, 1);   	// Baton
				GivePlayerWeapon(playerid, 17, 10);		// Teargas
				GivePlayerWeapon(playerid, 22, 170);    // 10 clips and a 9mm
			}
		}
		case DIALOG_DUTY_FBI:{
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{

				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_FBI_SKIN, DIALOG_STYLE_TABLIST, "Agent skins", "Caucasian MIB agent\tMale\nAfro-American MIB agent\tMale\nSWAT special forces\tMale\nFBI agent\tMale\nAfro-American bouncer\tMale\nCaucasian Bouncer\tMale\nOriental businesswoman\tFemale\nCaucasian businesswoman\tFemale", "Duty", "Cancel");
					}
				    case JOB_FBI:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");

				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);

				        ResetPlayerWeapons(playerid);
					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_FBI_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 165;}
					case 1:{skin = 166;}
					case 2:{skin = 258;}
					case 3:{skin = 286;}
					case 4:{skin = 163;}
					case 5:{skin = 164;}
					case 7:{skin = 141;}
					case 8:{skin = 150;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_FBI, playerid);
		        SetPlayerColor(playerid, COLOR_FBI);
				GivePlayerWeapon(playerid, 23, 170);    // 10 clips and a silenced 9mm
				
			}
		}
		case DIALOG_DUTY_DMV:{
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{

				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_DMV_SKIN, DIALOG_STYLE_TABLIST, "DMV skins", "Airport ground worker\tMale\nSecurity guard\tMale", "Duty", "Cancel");
					}
				    case JOB_DMV:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");

				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);
				        ResetPlayerWeapons(playerid);

					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_DMV_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 16;}
					case 1:{skin = 71;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_DMV, playerid);
		        SetPlayerColor(playerid, COLOR_DMV);
		        GivePlayerWeapon(playerid, 17, 10);		// Teargas
			}
		}
		case DIALOG_DUTY_NEWS:{
		    if(response){
		        // TODO: Check if has passport
		        switch(GetGVarInt("job", playerid)){
				    case JOB_NONE:{

				        // TODO: Check for crimes, warrents, etc

				        ShowPlayerDialog(playerid, DIALOG_DMV_SKIN, DIALOG_STYLE_TABLIST, "Network skins", "Hispanic businesswoman\nCaucasian Businesswoman\nBusinessman\nRich woman", "Duty", "Cancel");
					}
				    case JOB_NEWS:{
						// Get skin from character record
						new character_query[42 + MAX_SQL_INTEGER + 1];
				        format(character_query, sizeof(character_query), "SELECT skin_id FROM character WHERE id = %i", GetGVarInt("character_id", playerid));
				        new Result:id_result = sql_query(sqlHandle, character_query);
				        new skin_id = sql_get_field_assoc_int(id_result, "skin_id");

				        setPlayerSkin(playerid, skin_id);
						SetGVarInt("job", JOB_NONE, playerid);
				        SetPlayerColor(playerid, COLOR_WHITE);
				        ResetPlayerWeapons(playerid);

					}
				    default:{SendClientMessage(playerid, COLOR_ERROR, "SERVER: You can not use this pickup as long as you are on another job.");}
				}
			}
		}
		case DIALOG_NEWS_SKIN:{
		    if(response){
		        new skin;
		        switch(listitem){
					case 0:{skin = 148;}
					case 1:{skin = 150;}
					case 2:{skin = 187;}
					case 3:{skin = 219;}
				}
				setPlayerSkin(playerid, skin);
				SetGVarInt("job", JOB_NEWS, playerid);
		        SetPlayerColor(playerid, COLOR_NEWS);
		        GivePlayerWeapon(playerid, 43, 1);		// Camera
			}
		}
	}
	
	return 0; // MUST return 0 here, just like OnPlayerCommandText.
}


public OnPlayerClickPlayer(playerid, clickedplayerid, source)	// TODO for 0.0a
{
	return 1;
}
public OnEnterExitModShop(playerid, enterexit, interiorid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerGiveDamage(playerid, damagedid, Float: amount, weaponid)	// TODO for 0.0a
{
	return 1;
}

public OnPlayerTakeDamage(playerid, issuerid, Float: amount, weaponid)	// TODO for 0.0a
{
	return 1;
}


// discord-connector events
public DCC_OnMessageCreate(DCC_Message:message){

	// Originating Discord channel
	new DCC_Channel:channel;
	DCC_GetMessageChannel(message, channel);

	// Originating Discord user
	new DCC_User:author;
	DCC_GetMessageAuthor(message, author);

	// Message content
	new str[128 + 1];   // Orignal was 256
	new command[32 + 1], params[128 + 1];
	DCC_GetMessageContent(message, str);
	sscanf(str, "s[32]s[128]", command, params);    // This string is to small some times, and throws an error BROKEN TODO

	// Ignore bots
	new bool:isBot;
	DCC_IsUserBot(author, isBot);
	if(isBot){

		return 1;
	}
	if(!strcmp(command, "!players")){
        new players_string[14 + (2 * MAX_PLAYERS) + (MAX_PLAYER_NAME * MAX_PLAYERS) + 1] = "Online players:";
        for(new playerid, a = GetMaxPlayers(); playerid < a; playerid++){
			if(IsPlayerConnected(playerid)){
			    format(players_string, sizeof(players_string), "%s %s,", players_string, getPlayerName(playerid));
			}
		}
		strdel(players_string, strlen(players_string), strlen(players_string));  // Delete trailing comma
        DiscordSendChannelMessage(channel, players_string);
        print("Should be sending players");
	}
	else if(!strcmp(command, "!ip")){
		DiscordSendChannelMessage(channel, "ecrp.h0v1n8.nl:6666");
	}
	else{
	    if(channel == echoChannel){
			sendToChat(-1, CHAT_GLOBAL, str, 0, author);
		}
	}

	/*// Beyond this point, don't respond to commands on foreign guilds.
	if(guild != homeGuild){

		return 1;
	}*/

	// Beyond this point, only respond to privilaged channels.
	if(channel != adminEchoChannel && channel != adminChannel && channel != managementChannel){

		return 1;
	}


	//  Test command.
	if(!strcmp(command, "!test", true)){
		DiscordSendChannelMessage(channel, "Works.");
		print("Some said !test in the echo channel.");
	}

	return 1;
}


// Entrypoint
main(){
	// Credits
	print("\n*----------------------------------*");
	print(" RPFW");
	new message[21 + 20 + 1];
	format(message, sizeof(message), " Role-play framework v%s", MODE_NAME);
	print(message);
	//printf("System load average: %f",loadavg());	// Linux only   TODO: test
	print("*----------------------------------*\n");
}
