/*
Climb v2.0a2
Copyright (C) 2006 Ian (Juan) Cammarata

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

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

You should have received a copy of the GNU General Public License along with this program; go to http://www.opensource.org/licenses/gpl-license.php
*/
/*
*To do:
*  Recalc limiter/timeout on stats pages.
*  Flags for weapons used
*  Save user/pass login data for sessions (don't require login ever map change)
*  Account management for accounts with user/pass login. email, change password, password recovery
*  Unsolid as function of position and velocity
*  Respawn vehicles if they go out of the map
*===============================================================================
*	Climb v2.0a2
*	Created by Ian (Juan) Cammarata
*	http://ian.cammarata.us
*	AMXX 1.76
*	3/2/2007 1:23:06 AM
*===============================================================================
*	Description:
*		This plugin is designed for use in climbing maps like those available from
*		www.kreedz.com.  If you want to disable this plugin for the duration of a
*		map you can set cvar "climb 0", this will disable the ability to make and
*		recall checkpoints and the automatic respawning.
*===============================================================================
*	Commands:
*		say /checkpoint		:	Save your position
*		say /gocheck			:	Teleport you to your last saved position
*		say /stuck				:	Teleport you to your previous checkpoint in case
*												you get stuck in a wall
*===============================================================================
*	Cvars:
*		climb <1|0> : Enables|Disables all climb.amx functionality.
*		climb_boost <1|0> : Enables|Disables boosting.
*		climb_cpprice <0|...> : Set to dollar amount for cost of checkpoints.
*		climb_startmoney <1337|0-16000> : Money amount set when players start timer.
*
*		climb_render <0|1> : Changes unsolid rendering style, 0=classic, other=new more see through rendering.
*		climb_sounds <1|0> : Enables|Disables sounds build in to the Climb plugin.
*
*		climb_webmod <0|1> : Disables|Enables web mod interoperability.
*		climb_stats_path <NULL|...> : Path to save generated stats pages. (If using
*		                              WebMod, enter path to webmods www folder.)
*		climb_stats_url <NULL|...> : URL to send clients to for stats pages.  Only
*		                             use if you have a web server running on the
*		                             same machine as the game server.
*		                             IF WEBMOD=1 THIS IS IGNORED.
*		climb_stats_hsurl <NULL|...> : "%s" will be replaced with map name.  Set
*		                               to send client to URL if you have a page
*		                               that automatically generates high scores via
*		                               direct DB access.
*		                               IF WEBMOD=1 THIS IS IGNORED.
*		climb_stats_msg <NULL|...> : Message to display on scoreboard. Use HTML
*		
*		ip_internal <localhost|...> : Set to internal network IP or hostname. (If server behind NAT)
*		ip_external <localhost|...> : Set to external network IP or hostname.
*		
*		climb_msg_r <0|0-255> : Display message red value.
*		climb_msg_g <150|0-255> : Display message green value.
*		climb_msg_b <250|0-255> : Display message blue value.
*		climb_msg_x <0.05|0-1> : Display message x position.
*		climb_msg_y <0.5|0-1> : Display message y position.
*		
*		*The following cvars are only read at plugin load, changes will not take
*		 effect until the map is changed.
*		climb_save <1|0> : Enables|Disables saving of stats to a database.
*		
*		climb_db_type <sqlite|mysql> : Database type.
*		climb_db_host <127.0.0.1> : Database host name/ip.
*		climb_db_user <NULL|...> : Database user name.
*		climb_db_pass <NULL|...> : Database password.
*		climb_db_name <climb|...> : Database name.
*		climb_db_prefix <climb_|...> : Table name prefix.
*===============================================================================
*	Requirements:
*		Modules:
*			Engine
*			Fakemeta
*			DB Module (MySQL or SQLite/Only need to use stats.)
*		AMXX 1.76 or higher
*===============================================================================
*	Notes:
*		Enjoy!
*===============================================================================
*	Change Log:
*		Key (+ added | - removed | c changed | f fixed | r refactored)
*
*		v2.0a2 (FEB 16, 2007)
*			+: 2 addition boost types. Super jump and double jump. (No partner needed for boosting anymore)
*			+: Boosts are counted. (Not logged in DB yet.)
*			+: Stats saving in database. MySQL or SQLite. (MySQL not tested yet.  Only works with single db module enabled)
*			+: Players now get rewarded for finishing even without starting the timer.
*			+: Cvars for message color and position. (climb_msg_r, ..._g, ..._b, ..._x, ..._y)
*			+: Cvar climb_startmoney - Value to set money to when players start timer.
*			+: Cvar climb_stats_msg - Message to display on the HTML scoreboards.
*			+: Cvar climb_unsolid_type - Change rendering style for unsolid players.
*			+: Cvar climb_sounds - Disabled Climb plugin sounds.
*			+: Several cvars for database settings.
*			+: All players spawn at spawn point closest to the start button. (Eliminates cheat in maps with a spawn point at the finish button.)
*			+: Additional spawns added during map load to fix maps without enough spawns.
*			+: Timeouts for respawning and boosting.
*			+: Command "help". Displays HTML help pages.
*			-: AMX compatability.  (AMX is officially dead)
*			c: Cvars now prefixed with climb_ instead of amx_.
*			c: Minor code optimizations.
*			c: Command "Boost" now takes players to the boost help page instead of activating solid boost.
*			r: All internal cvar handling is now done with pointers. (With the acception of cvars used only during init functions.)
*			r: Solid boost only lasts 15 seconds at a time now since its usage is being tracked.
*			r: Major internal rewrite (Probably added a few bugs. More major rewrites coming in next release.)
*		v1.9.19 (AUG 02, 2006)
*			+: New public cvar 'climb_version' to aid in searching for server with this plugin.
*			+: Show checkpoints, gochecks, and finish stats (Stats will be more extensive in next version) with finish announcement.
*			-: Global chat.  Use AllChat plugin instead.
*			-: Cvar: amx_climb_globchat
*			c: The /spec command now obeys the cvar "allow_spectators", clients with reserved slot always allowed.
*			c: Shows number of checks and gocheck on client finish announcement.
*			f: Bug caused by using stop command while paused.
*			r: Client commands reworked.  Everything functions the same, but with more functionality.  For example the gc command can be entered as any of the following: gc, /gc, \gc, say gc, say /gc, say \gc, say_team gc, etc...  
*		v1.9.18 (MAY 15, 2006)
*			r: Recoded the way health is handled.  Should work for any map now.
*		v1.9.17 (APR 07, 2006)
*			c: Plugin is now compatible with both AMX and AMXX.
*			c: The /spectate command can now be used by anyone.  Admin no longer required.
*		v1.9.16a
*			f: Added health fix for kz_cfl_yamakasi.
*			c: New CSS theme for html scoreboard.
*		v1.9.16
*			+: Counts usage of CP and GC commands(only displayed on html scoreboard).
*			f: Added health fix for kz_lighthouse and kz_phoogi.
*		v1.9.15
*			+: Colorized global chat. ADMIN_RESERVATION has name in green instead of team color.
*			+: /stop command.  Resets timer to zero, end climbing session.
*			c: Reset function.  /stop + /respawn.  Client is now reset automatically when pressing the start button if they are not already started.
*			f: YOU CAN'T GET STUCK ANYMORE!!! (Well almost. Doing gocheck a second time gets you unstuck.)
*			f: Global chat is now logged correctly and shows up in HLSW.
*		v1.9.14
*			+: Hook is taken away when client starts timer, given back if they reset.
*			+: Max health fixes for several new maps.
*			+: Sort clients on CS scoreboard by climb rank.
*			+: Cvar amx_climb_boost - enable\disable boosting.
*			+: Cvar amx_climb_cpprice - charge money for checkpoints.
*			+: Custom configurable commands based on events. (climb.ini)
*			+: Global chat. cvar: amx_climb_globchat <1|0>
*			+: Client climb time is now shown using the round time HUD sprites.
*			-: Command /mytime, not needed because of HUD sprite timer.
*			-: Auto time display every 30 seconds, not needed because of HUD sprite timer.
*			c: Added another start button sound.
*			c: Super healing doors no longer removed. (Needed for shortcuts in some maps.)
*			f: Admins can't be alive as spectator even with 3rd party respawn plugin.
*			f: Keep godmode after respawning if finished map and not reset.
*			f: Admins VIP display on CS scoreboard is now updated whenever clients connect.
*			f: Health charger minimaps in kz_real_skyscraper & kz_northpole_b01.
*			f: Not teleporting to exact cp position if another client is on your cp.
*			f: Respawning outside of maps sometimes immediately after leaving spectator.
*/
#include <amxmodx>
#include <fun>
#include <amxmisc>
#include <engine>
#include <fakemeta>
#include <sqlx>
#include <string2>
#include <cstrike>
#include <cstrike2>

#define SF_MAX 50 //Maximum number of start/finish commands in climb.ini
#define VERSION "2.0a2"

//DB variables
new Handle:db_tuple,db_prefix[10],bool:climb_save

//Client flags arrays
new Float:origins[32][41],timer[32][13],time_stamps[33][2],Float:post_think_vel[32][3]

//Temp save vars
new steamid[32][32],savepos=0,Float:originssave[32][41],timersave[32][13]

//Touch for unsolid clients
new trig[100],trignum=0,brk[50],brknum=0

//Other stuff
new hooked[32],hp=100
new sfactions[SF_MAX][50],sfcount=0
new score_ts,hscore_ts
new dyn_spawn_ids[32],dyn_spawn_count=-1,Float:spawn_tp_orig[3]

//Cvar Pointers
new p_climb,p_webmod,p_boost,p_cpprice
new p_msg_r,p_msg_g,p_msg_b,p_msg_x,p_msg_y
new p_sounds,p_render
new p_ip_internal,p_ip_external,p_port
new p_allow_spectators,p_startmoney
new p_stats_path,p_stats_url,p_stats_hsurl,p_stats_msg

//Model size
//standing 32x32x72
//crouched 32x32x36

//timestamps[id][x]
#define TS_SPAWN 0
#define TS_BOOST 1

//Tasks:
//#define TSK_AUTOHEAL 50		//50+ : Auto Heal
//#define TSK_AUTORSPN 100	//100+: Auto Respawn
//#define TSK_BOOSTTMR 150	//150+: Solid Boost Timer

//timer[id][x]:
#define TMR_CFLAGS 0	//Status
#define TMR_STARTD 1	//Start Time
#define TMR_FINISH 2	//Finish Time
#define TMR_CPSCNT 3	//CP Count
#define TMR_GCSCNT 4	//GC Count
#define TMR_BSTTME 5	//Best Time
#define TMR_BSTCPS 6	//Best CP
#define TMR_BSTGCS 7	//Best GC
#define TMR_SESFIN 8	//Finished this session
#define TMR_MAPFIN 9	//Total times finished this map
#define TMR_DBUSER 10 //Database player ID; 0=not registered; -1=not registered & shared steam id
#define TMR_BOOSTS 11 //Number of boosts used
#define TMR_WPNUSE 12 //Bit sum of used weapons

//Status timer[id][TMR_CFLAGS]&=x
#define CF_NULL   0	//Used to clear flags in change_status() and change_boost() functions
#define CF_STOP   (1<<0)	//STATUS FLAG: Not Started
#define CF_START  (1<<1)	//STATUS FLAG: Climbing
#define CF_PAUSE  (1<<2)	//STATUS FLAG: Paused
#define CF_SOLID       (1<<3)	//BOOST FLAG
#define CF_SUPER_JUMP  (1<<4)	//BOOST FLAG
#define CF_DOUBLE_JUMP (1<<5)	//BOOST FLAG
#define CF_SPAWN_NO_GC (1<<6)
#define CF_NO_SCOUT    (1<<7)
#define CF_NO_VIP      (1<<8)

public client_putinserver(id){
	if(get_pcvar_num(p_climb)&&!is_user_bot(id)){
		new ida[1]
		ida[0]=id
		set_task(20.0,"connect_advert",0,ida,1)
	//Set CF_STOP status
		change_status(id,CF_STOP)
	//search steamid to position reference for match
		new searchid[32]
		get_user_authid(id,searchid,31)
		for(new i=0;i<32;i++)
			if(equal(searchid,steamid[i])){
	//load origins & timer array if match found
				origins[id]=originssave[i]
				timer[id]=timersave[i]
				return PLUGIN_CONTINUE
			}
		if(climb_save&&timer[id][TMR_DBUSER]<1)
			set_task(5.0,"auto_login",0,ida,1)
	}
	return PLUGIN_CONTINUE
}
public connect_advert(ida[1]){
	new id=ida[0]
	new msg[51]
	formatex(msg,50,"^x04 ^t^t^t^t^t^t^tThis server is using Climb v%s by:",VERSION)
	saytext(id,id,msg)
	saytext(id,id,"^x04 ^t^t^t^t^t^t^tIan (Juan) Cammarata (http://ian.cammarata.us)")
}
public auto_login(ida[1])
	return client_cmd(ida[0],"login")
public client_disconnect(id){
	if(get_pcvar_num(p_climb)&&!is_user_bot(id)){
		savepos++
		if(savepos==31)savepos=0
		new saveid[32]
		get_user_authid(id,saveid,32)
	//erase previous save if exists
		for(new i=0;i<32;i++)if(equal(saveid,steamid[i]))steamid[i]=""
	//save steamid to position reference
		steamid[savepos]=saveid
	//save origins
		originssave[savepos]=origins[id]
	//save timer array/Pause if running 
		if(timer[id][TMR_CFLAGS]&CF_START)change_status(id,CF_PAUSE)
		timersave[savepos]=timer[id]
	//clear origins for new client in that slot
		for(new i=0;i<8;i++)origins[id][i]=0.0
		for(new i=0;i<11;i++)timer[id][i]=0
		hooked[id]=0
	}
	return PLUGIN_HANDLED
}
public check(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		new cpprice=get_pcvar_num(p_cpprice)
		if(cpprice>0){
			new cash=cs_get_user_money(id)
			if(cpprice>cash){
				clmsg(id,"You don't have enough cash for more checkpoints.")
				client_print(id,print_chat,"You don't have enough cash for more checkpoints.")
				return PLUGIN_HANDLED
			}
			cs_set_user_money(id,cash-cpprice)
		}
		if(!hooked[id]){
			new Float:vel[3]
			entity_get_vector(id,EV_VEC_velocity,vel)
			if(vel[2]>=0){
				new Float:coords[3]
				entity_get_vector(id,EV_VEC_origin,coords)
				if(coords[0]||coords[1]||coords[2]){
					for(new i=39;i>3;i--) origins[id][i]=origins[id][i-4]
					for(new i=0;i<3;i++) origins[id][i]=coords[i]
					origins[id][3]=entity_get_float(id, EV_FL_gravity)
					new msg[100]="Checkpoint saved."
					if(timer[id][TMR_CFLAGS]&CF_START){
						timer[id][TMR_CPSCNT]++
						formatex(msg,99,"Checkpoint saved. (%d CPS/ %d GCS/ %d Boosts)",timer[id][TMR_CPSCNT],timer[id][TMR_GCSCNT],timer[id][TMR_BOOSTS])
					}
					clmsg(id,msg)
				}
				else clmsg(id,"Can not save checkpoint at current location.")
			}
			else clmsg(id,"You can't make checkpoints while falling.")
		}
		else clmsg(id,"You can't make checkpoints while using hook.")
	}
	return PLUGIN_HANDLED
}
public gocheck(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		if(origins[id][0]||origins[id][1]||origins[id][2]){
			new Float:coords[3]
			for(new i=0;i<3;i++)coords[i]=origins[id][i]
			entity_set_float(id,EV_FL_gravity,origins[id][3])
			teleport(id,coords)
			new msg[100]="Checkpoint restored."
			if(timer[id][TMR_CFLAGS]&CF_START){
				timer[id][TMR_GCSCNT]++
				formatex(msg,99,"Checkpoint restored. (%d CPS/ %d GCS/ %d Boosts)",timer[id][TMR_CPSCNT],timer[id][TMR_GCSCNT],timer[id][TMR_BOOSTS])
			}
			clmsg(id,msg)
		}
		else{
			clmsg(id,"You must make a checkpoint first.")
			return 0
		}
	}
	return 1
}
public stuck(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		if(origins[id][4]||origins[id][5]||origins[id][6]){
			new Float:coords[3]
			for(new i=0;i<36;i++) origins[id][i]=origins[id][i+4]
			for(new i=0;i<3;i++) coords[i]=origins[id][i]
			entity_set_float(id,EV_FL_gravity,origins[id][3])
			teleport(id,coords)
			new msg[100]="Previous checkpoint restored."
			if(timer[id][TMR_CFLAGS]&CF_START){
				timer[id][TMR_CPSCNT]--
				formatex(msg,99,"Previous checkpoint restored. (%d CPS/ %d GCS/ %d Boosts)",timer[id][TMR_CPSCNT],timer[id][TMR_GCSCNT],timer[id][TMR_BOOSTS])
			}
			clmsg(id,msg)
			new cpprice=get_pcvar_num(p_cpprice)
			if(cpprice>0){
				new cash=cs_get_user_money(id)
				cs_set_user_money(id,cash+cpprice)
			}
		}
		else clmsg(id,"You have no previous checkpoints remaining.")
	}
	return PLUGIN_HANDLED
}
//Respawn client when they die
public death_msg(){
	if (get_pcvar_num(p_climb)){
		new id=read_data(2)
		new ida[1]
		ida[0]=id
		if(task_exists(100+id))remove_task(100+id)
		if(isct(id)){
			set_task(0.2,"respawn",100+id,ida,1)
		}
	}
	return PLUGIN_HANDLED
}
public change_status(id,newstat){
	new cflags=timer[id][TMR_CFLAGS]
	if(cflags&CF_STOP)timer[id][TMR_CFLAGS]-=CF_STOP
	else if(cflags&CF_START)timer[id][TMR_CFLAGS]-=CF_START
	else if(cflags&CF_PAUSE)timer[id][TMR_CFLAGS]-=CF_PAUSE
	if(newstat&CF_PAUSE){
		if(cflags&CF_PAUSE){
			timer[id][TMR_CFLAGS]+=CF_START
			timer[id][TMR_STARTD]=get_systime()-timer[id][TMR_STARTD]
			set_entity_flags(id,FL_FROZEN,0)
			unsolid(id)
			entity_set_float(id,EV_FL_gravity,origins[id][40])
			clmsg(id,"UNPAUSED")
		}
		else if(cflags&CF_START){
			cl_pause(id)
			timer[id][TMR_CFLAGS]+=CF_PAUSE
		}
		else{
			clmsg(id,"You must start the timer before you can pause.")
			timer[id][TMR_CFLAGS]=cflags
		}
	}
	else timer[id][TMR_CFLAGS]+=newstat
	return PLUGIN_HANDLED
}
public cl_pause(id){
	timer[id][TMR_STARTD]=get_systime()-timer[id][TMR_STARTD]
	set_entity_flags(id,FL_FROZEN,1)
	unsolid(id)
	set_rendering(id,kRenderFxGlowShell,0,0,255,kRenderTransColor,1)
	origins[id][40]=entity_get_float(id,EV_FL_gravity)
	entity_set_float(id,EV_FL_gravity,0.0)
	clmsg(id,"PAUSED")
	return PLUGIN_HANDLED
}
public stop(id){
	if(get_pcvar_num(p_climb)&&timer[id][TMR_CFLAGS]&CF_START/*&&notpaused(id)*/){
		change_status(id,CF_STOP)
		sfexec(id,4)	//Execute commands from start/finish config
	}
	return PLUGIN_HANDLED
}
public reset(id){
	if(get_pcvar_num(p_climb)&&check_timeout(id,get_systime(),time_stamps[id][TS_SPAWN],5)){
		stop(id)
		frespawn(id)
	}
	return PLUGIN_HANDLED
}
public frespawn(id){
	if(get_pcvar_num(p_climb)&&check_timeout(id,get_systime(),time_stamps[id][TS_SPAWN],5)){
		if(get_user_team(id)==3)cs_set_user_team(id,2)
		timer[id][TMR_CFLAGS]+=CF_SPAWN_NO_GC
		cs_user_spawn(id)
	}
	return PLUGIN_HANDLED
}
public respawn(ida[]){
	new id=ida[0]
	if(!get_user_team(id)||is_user_alive(id)||!isct(id))
		return PLUGIN_HANDLED
	cs_user_spawn(id)
	//gocheck(id)
	return PLUGIN_HANDLED
}
public spawned(id){
	if(get_pcvar_num(p_climb)){
	//Block Buying
		new cflags=timer[id][TMR_CFLAGS]
		set_msg_block(get_user_msgid("StatusIcon"),2)
		if(is_user_alive(id)){
		//Set them to CT if they're a spectator
			if(get_user_team(id)==3)cs_set_user_team(id,2)
			sortcssb()
		//If they are paused freeze them again.
			if(cflags&CF_PAUSE)cl_pause(id)
			set_user_health(id,hp)
		//Only admins can shoot other clients.
			//if(!(get_user_flags(id)&ADMIN_SLAY))set_user_hitzones(id,0,0)	//Keeps players from seeing eachothers names.
		//Remove stuff dropped on death
			remove_entity_name("weaponbox")
			remove_entity_name("item_thighpack")
		//Execute commands from start/finish config
			sfexec(id,1)
		//Show admin as VIP on Scoreboard.
			if(get_user_flags(id)&ADMIN_SLAY)cs_set_user_scoreattrib(id,4)
		//Equip Task
			new ida[1]
			ida[0]=id
			set_task(0.1,"equip",_,ida,1)
			time_stamps[id][TS_SPAWN]=get_systime()
		//Teleport to safe place/old primary spawn position
			if(cflags&CF_SPAWN_NO_GC){
				if(vector_distance(spawn_tp_orig,Float:{0,0,0}))teleport(id,spawn_tp_orig)
				timer[id][TMR_CFLAGS]-=CF_SPAWN_NO_GC
			}
			else if(!gocheck(id))if(!(cflags&CF_START&&!getusertime(id)))if(vector_distance(spawn_tp_orig,Float:{0,0,0}))teleport(id,spawn_tp_orig)
		}
	}
	else set_msg_block(get_user_msgid("StatusIcon"),0)
	return PLUGIN_CONTINUE
}
public equip(ida[]){	//Give equipment to client, delayed to prevent crash
	new id=ida[0]
	for(new i=0;i<2;i++)give_item(id,"weapon_scout")
	give_item(id,"weapon_c4")
	cs_set_user_nvg(id)
}
public check_timeout(id,time1,time2,freq){
	if(time1<time2)return 1
	new dif=time1-time2
	if(dif<freq){
		new msg[76]
		format(msg,75,"You must wait %d more seconds to use this command again.",freq-dif)
		if(id>0)clmsg(id,msg)
		return 0
	}
	return 1
}
public change_boost(id,newboost){	//Change Boost flags
	if(!(cvar_enabled(id,p_boost)&&isalive(id))&&notpaused(id))return PLUGIN_HANDLED
	new msg[151],rmflag,cflags=timer[id][TMR_CFLAGS]
	if(cflags&CF_SOLID){
		if(task_exists(150+id))remove_task(150+id)
		rmflag=CF_SOLID
		msg="Solid Boost Disabled.^n"
	}
	else if(cflags&CF_SUPER_JUMP){
		rmflag=CF_SUPER_JUMP
		msg="Super Jump Disabled.^n"
	}
	else if(cflags&CF_DOUBLE_JUMP){
		rmflag=CF_DOUBLE_JUMP
		msg="Double Jump Disabled.^n"
	}
	if(rmflag>0)timer[id][TMR_CFLAGS]-=rmflag
	if(!(rmflag&newboost)&&check_timeout(id,(cflags&CF_START?getusertime(id):get_systime()),time_stamps[id][TS_BOOST],5)){
		if(newboost==CF_SOLID){
			format(msg,150,"%sSolid Boost Enabled.",msg)
			new ida[1]
			ida[0]=id
			set_task(15.0,"solid_boost_timer",150+id,ida,1)
			if(timer[id][TMR_CFLAGS]&CF_START)timer[id][TMR_BOOSTS]++
		}
		if(newboost==CF_SUPER_JUMP)format(msg,150,"%sSuper Jump Enabled.",msg)
		if(newboost==CF_DOUBLE_JUMP)format(msg,150,"%sDouble Jump Enabled.",msg)
		timer[id][TMR_CFLAGS]+=newboost
	}
	if(strlen(msg))clmsg(id,msg)
	return PLUGIN_HANDLED
}
public solid_boost_timer(ida[]){	//Task to auto disable solid boost after timeout
	//if(timer[id][TMR_CFLAGS]&CF_SOLID)solid_boost(id) //Shouldn't need the if statement
	change_boost(ida[0],CF_SOLID)
	return PLUGIN_HANDLED
}
//Semi-clip, and entity touch detection for non-solid clients
//* Maybe move this to client prethink
public server_frame(){
	if(get_pcvar_num(p_climb)){
		new players[32],num,i,j,Float:c1[3],Float:c2[3]
		new Float:c1z[3],Float:c2z[3]
		new id_i,id_j,cflags_i,cflags_j
		get_players(players,num,"ac")
		if(trignum){
			for(i=0;i<num;i++){
				id_i=players[i]
				if(!playersolid(id_i)){
					for(j=0;j<trignum;j++){
						id_j=trig[j]
						entity_get_vector(id_j,EV_VEC_absmin,c2)
						entity_get_vector(id_j,EV_VEC_absmax,c2z)
						entity_get_vector(id_i,EV_VEC_origin,c1)
						if(c2[0]<=c1[0]&&c2z[0]>=c1[0]&&c2[1]<=c1[1]&&c2z[1]>=c1[1]&&c2[2]<=c1[2]&&c2z[2]>=c1[2]){
							fake_touch(id_j,id_i)
							j=trignum
						}
					}
				}
			}
		}
		if(brknum){
			for(i=0;i<num;i++){
				id_i=players[i]
				if(!playersolid(id_i)){
					for(j=0;j<brknum;j++){
						id_j=brk[j]
						entity_get_vector(id_j,EV_VEC_absmin,c2)
						entity_get_vector(id_j,EV_VEC_absmax,c2z)
						entity_get_vector(id_i,EV_VEC_origin,c1)
						c1[0]-=26
						c1z[0]=c1[0]+32
						c1[1]-=16
						c1z[1]=c1[1]+32
						c1[2]-=40
						c1z[2]=c1[2]+50
						if(((c2[0]<c1[0]<c2z[0])||(c2[0]<c1z[0]<c2z[0])||(c1[0]<c2[0]<c1z[0])||(c1[0]<c2z[0]<c1z[0]))
						 &&((c2[1]<c1[1]<c2z[1])||(c2[1]<c1z[1]<c2z[1])||(c1[1]<c2[1]<c1z[1])||(c1[1]<c2z[1]<c1z[1]))
						 &&((c2[2]<c1[2]<c2z[2])||(c2[2]<c1z[2]<c2z[2])||(c1[2]<c2[2]<c1z[2])||(c1[2]<c2z[2]<c1z[2]))
						){
							fake_touch(id_j,id_i)
							j=brknum
						}
					}
				}
			}
		}
		c1z[0]=0.0
		c1z[1]=0.0
		c2z[0]=0.0
		c2z[1]=0.0
		//new Float:c1d[3],Float:c2d[3],xyadd,zadd
		for(i=0;i<num;i++){
			id_i=players[i]
			cflags_i=timer[id_i][TMR_CFLAGS]
			if(!(cflags_i&CF_PAUSE)){
				//solid(players[i])
				entity_get_vector(id_i,EV_VEC_origin,c1)
				//entity_get_vector(players[i],EV_VEC_velocity,c1d)
				c1z[2]=c1[2]
				c1[2]=0.0
				for(j=0;j<num;j++){
					id_j=players[j]
					cflags_j=timer[id_j][TMR_CFLAGS]
					if(!(cflags_j&CF_SOLID)&&i!=j){
						entity_get_vector(id_j,EV_VEC_origin,c2)
						//entity_get_vector(players[j],EV_VEC_velocity,c2d)
						c2z[2]=c2[2]
						c2[2]=0.0
						if(vector_distance(c1,c2)<90&&vector_distance(c1z,c2z)<110){
							if(!(cflags_i&CF_SOLID&&cflags_j&CF_SOLID)){//&&vector_distance(c1,c2)>25&&vector_distance(c1z,c2z)>30)){
								unsolid(id_i)
								unsolid(id_j)
								j=num
							}
						}
					}
					if(j+1==num)solid(id_i)
				}
			}
		}
	}
	return PLUGIN_CONTINUE
}
public client_PreThink(id){
	if(!(get_pcvar_num(p_climb)&&get_pcvar_num(p_boost)&&isalive(id)))return PLUGIN_CONTINUE
	new cflags=timer[id][TMR_CFLAGS],bool:onground=false
	if(get_entity_flags(id)&FL_ONGROUND)onground=true
	if(get_user_button(id)&IN_JUMP){
		if(cflags&CF_SUPER_JUMP&&onground){
			entity_get_vector(id,EV_VEC_velocity,post_think_vel[id])
			if(post_think_vel[id][0]!=0.0||post_think_vel[id][1]!=0.0){
				velocity_by_aim(id,600,post_think_vel[id])
				if(post_think_vel[id][2]<250.0)post_think_vel[id][2]=250.0
				if(post_think_vel[id][2]>350.0)post_think_vel[id][2]=350.0
			}
			else post_think_vel[id]=Float:{0.0,0.0,0.0}
		}
		else if(cflags&CF_DOUBLE_JUMP){
			if(!onground&&!(get_user_oldbutton(id)&IN_JUMP)){
				entity_get_vector(id,EV_VEC_velocity,post_think_vel[id])
				if(is_finished(id)||post_think_vel[id][2]>-500)post_think_vel[id][2]=250.0
				else post_think_vel[id]=Float:{0.0,0.0,0.0}
			}
		}
	}
	return PLUGIN_CONTINUE
}
public client_PostThink(id){
	if(!(get_pcvar_num(p_climb)&&get_pcvar_num(p_boost)&&isalive(id)))return PLUGIN_CONTINUE
	if(_:get_distance(_:post_think_vel[id],{0,0,0})){
		entity_set_vector(id,EV_VEC_velocity,post_think_vel[id])
		post_think_vel[id]=Float:{0.0,0.0,0.0}
		if(timer[id][TMR_CFLAGS]&CF_START){
			timer[id][TMR_BOOSTS]++	//Increment client boost count
			time_stamps[id][TS_BOOST]=getusertime(id) //Timeout based on timer so they can't pause to avoid timeout
			change_boost(id,CF_NULL)
		}
		else if(!is_finished(id)){
			time_stamps[id][TS_BOOST]=get_systime()
			change_boost(id,CF_NULL)
		}
	}
	return PLUGIN_CONTINUE
}
public is_finished(id){
	new cflags=timer[id][TMR_CFLAGS]
	if(cflags&CF_STOP&&(get_user_flags(id)&ADMIN_RESERVATION||timer[id][TMR_SESFIN]>0))
		return 1
	return 0
}
public traceline(Float:v1[3], Float:v2[3], noMonsters, entity){
	if(get_pcvar_num(p_climb)){
		new id=entity
		new entity2=get_tr(TR_pHit)
		if(id>0&&entity2>0){
			if(is_user_alive(id)){
				new class[32]
				entity_get_string(entity2,EV_SZ_classname,class,32)
				new Float:orig1[3],Float:orig2[3]
				entity_get_vector(id,EV_VEC_origin,orig1)
				get_brush_entity_origin(entity2,orig2)
				//if(get_user_button(id)&IN_USE&&get_user_aiming(id,a,b)<70&&equal(class,"func_button")){
				if(get_user_button(id)&IN_USE&&vector_distance(orig1,orig2)<95&&equal(class,"func_button")){
					new targ[32]
					entity_get_string(entity2,EV_SZ_target,targ,32)
				//Timer Start
					//if((!(timer[id][TMR_CFLAGS]&CF_START)||getusertime(id)>4)&&/*(timer[id][TMR_CFLAGS]&CF_STOP||timer[id][TMR_CFLAGS]==TMR_CFLAGS_FNSH)&&(*/equal(targ,"counter_start")||equal(targ,"clockstartbutton")||equal(targ,"firsttimerelay")/*)*/){
					if((!(timer[id][TMR_CFLAGS]&CF_START)||check_timeout(id,get_systime(),time_stamps[id][TS_SPAWN],5))&&(equal(targ,"counter_start")||equal(targ,"clockstartbutton")||equal(targ,"firsttimerelay"))){
						new Float:orig[3],Float:view[3]
					//Erase checkpoints
						for(new i=0;i<8;i++)origins[id][i]=0.0
					//Set all associated variables
						timer[id][TMR_STARTD]=get_systime()
						change_status(id,CF_START)
						timer[id][TMR_CPSCNT]=0
						timer[id][TMR_GCSCNT]=0
						timer[id][TMR_BOOSTS]=0
					//Respawn client to clear items.
						entity_get_vector(id,EV_VEC_origin,orig)
						entity_get_vector(id,EV_VEC_angles,view)
						cs_user_spawn(id)
						entity_set_vector(id,EV_VEC_origin,orig)
						entity_set_vector(id,EV_VEC_angles,view)
						delay_duck(id)
						cs_set_user_money(id,get_pcvar_num(p_startmoney))
					//Play random start sound and text messages
						if(get_pcvar_num(p_sounds))switch(random_num(0,2)){
							case 0:client_cmd(id,"spk barney/beertopside")
							case 1:client_cmd(id,"spk scientist/c3a2_sci_portopen")
							case 2:client_cmd(id,"spk barney/c1a2_ba_climb")
						}
					//Disable Boosts
						change_boost(id,CF_NULL)
						time_stamps[id][TS_BOOST]=0
						clmsg(id,"Timer started. Go Go Go!!!")
						client_print(id,print_chat,"Timer started. Go Go Go!!!")
					//Update frags to reorder scoreboard
						sortcssb()
					//If stats save enabled warn unregistered clients to register
						if(climb_save)regwarn(id)
					//Execute commands from start/finish config
						sfexec(id,2)
						time_stamps[id][TS_SPAWN]=get_systime()
					}
				//Timer Stop
					else if(equal(targ,"counter_off")||equal(targ,"clockstopbutton")||equal(targ,"clockstop")){
						if(timer[id][TMR_CFLAGS]&CF_START){
						//Set client variables
							timer[id][TMR_FINISH]=get_systime()
							change_status(id,CF_STOP)
							timer[id][TMR_SESFIN]++
							timer[id][TMR_MAPFIN]++
						//Update frags to reorder scoreboard
							sortcssb()
						//Announce to client
							if(get_pcvar_num(p_sounds))client_cmd(0,"spk woop")
							clmsg(id,"Congratulations 1337 climber.")
							client_print(id,print_chat,"Congratulations 1337 climber.")
							new name[32],msg[21]
							formatex(msg,sizeof(msg)-1,"%s^t%s",getuserstatus(id),parsetime(getusertime(id)))
							//clmsg(id,msg)
							//client_print(id,print_chat,msg)
						//Announce to all
							get_user_name(id,name,32)
							client_print(0,print_chat,"%s^t%s^t(%d CPS/ %d GCS/ %d Boosts)^tCompleted (%d, %d)",name,msg,timer[id][TMR_CPSCNT],timer[id][TMR_GCSCNT],timer[id][TMR_BOOSTS],timer[id][TMR_SESFIN],timer[id][TMR_MAPFIN])
						//If new record
							if(getusertime(id)<timer[id][TMR_BSTTME]||timer[id][TMR_BSTTME]==0){
								timer[id][TMR_BSTTME]=getusertime(id)
								timer[id][TMR_BSTCPS]=timer[id][TMR_CPSCNT]
								timer[id][TMR_BSTGCS]=timer[id][TMR_GCSCNT]
							}
						//Execute commands from start/finish config
							sfexec(id,2)
						//If stats enabled, warn unregistered clients to register, or save for registered clients
							if(climb_save)
								if(!regwarn(id))
									db_save(id)
						}
						else if(timer[id][TMR_CFLAGS]&CF_STOP&&timer[id][TMR_SESFIN]==0){
							//change_status(id,CF_FINISH)
							timer[id][TMR_SESFIN]++
							new msg[100]="Congratulations, you finished! But you didn't start the timer. :("
							clmsg(id,msg)
							client_print(id,print_chat,msg)
							//Execute commands from start/finish config
							sfexec(id,2)
							//If stats save enabled warn unregistered clients to register
							if(climb_save)regwarn(id)
						}
					}
				}
			}
		}
	}
	return PLUGIN_CONTINUE
}
//Execute commands from start/finish config
public sfexec(id,clevent){
	new clstat[3],cflags=timer[id][TMR_CFLAGS]
	/*switch(timer[id][TMR_CFLAGS]){
		case TMR_CFLAGS_STOP: clstat="ns"
		case TMR_CFLAGS_STRT: clstat="st"
		case TMR_CFLAGS_FNSH: clstat="fn"
	}*/
	if(is_finished(id))clstat="fn"
	else if(cflags&CF_STOP)clstat="ns"
	else if(cflags&CF_START)clstat="st"
	//else if(timer[id][TMR_CFLAGS]&CF_FINISH)clstat="fn"
	//if(equal(clstat,"ns")&&(get_user_flags(id)&ADMIN_RESERVATION||timer[id][TMR_SESFIN]>0))clstat="fn"
	
	for(new i=0;i<sfcount;i++){
		new cmdstat[3],cmdevent[2],cmdtype[3],cmd[60],idtype[5]
		parse(sfactions[i],cmdstat,2,cmdevent,1,cmdtype,2,cmd,sizeof(cmd)-1,idtype,4)
		if((equali(cmdstat,clstat)||equali(cmdstat,"ay"))&&str_to_num(cmdevent)&clevent){
			if(equali(cmdtype,"sc")){
				if(equali(idtype,"uid"))format(cmd,sizeof(cmd)-1,cmd,get_user_userid(id))
				else{
					new name[24]
					get_user_name(id,name,sizeof(name)-1)
					format(name,sizeof(name),"^"%s^"",name)
					format(cmd,sizeof(cmd)-1,cmd,name)
					client_print(id,print_chat,cmd)
				}
				server_cmd(cmd)
			}
			else{
				client_cmd(id,cmd)
			}
		}
	}
	return 1
}
//Pad text for console score listing
public pad(pad){
	new j=4,str[50]=""
	for(new i=strlen(str);i<pad;i++){
		if(j==5){
			copy(str[i],1,"^t")
			j=0
		}
		else copy(str[i],1," ")
		j++
	}
	return str
}
//Update frags to reorder scoreboard
public sortcssb(){
	new players[32],num
	get_players_ordered(players,num)
	for(new i=0;i<num;i++){
		set_user_frags(players[i],0)
		cs_set_user_deaths(players[i],i+1)
	}
	return 1
}
//Returns array of client id's sorted by climb time
public get_players_ordered(players[32],&num){
	get_players(players,num,"c")
//Insertion Sort clients by finished time
	for(new i=1;i<num;i++){
		new j=i,tmp
		if(timer[players[i]][TMR_BSTTME]>0){
			tmp=players[i]
			while(j>0&&(timer[players[j-1]][TMR_BSTTME]==0?18000:timer[players[j-1]][TMR_BSTTME])>timer[tmp][TMR_BSTTME]){
				players[j]=players[j-1]
				j--
			}
			players[j]=tmp
		}
	}
//2nd Insertion Sort clients by climbing time
	for(new i=1;i<num;i++){
		new j=i,tmp
		if(getusertime(players[i])>0){
			tmp=players[i]
			while(j>0&&(getusertime(players[j-1])==0?18000:getusertime(players[j-1]))>getusertime(tmp)&&timer[players[j-1]][TMR_BSTTME]==timer[tmp][TMR_BSTTME]){
				players[j]=players[j-1]
				j--
			}
			players[j]=tmp
		}
	}
	return 1
}
//Make pathname for scoreboard html files
public pathname(filename[]){
	new path[100]
	get_pcvar_string(p_stats_path,path,79)
	format(path,99,"%s%s",path,filename)
	return path
}
public get_limit(){
	if(get_pcvar_num(p_webmod))
		return 0
	new url[100]
	get_pcvar_string(p_stats_url,url,99)
	if(strlen(url))
		return 0
	return 1
}
public get_url(id,file[]){
	new url[100]
	get_pcvar_string(p_stats_url,url,99)
	if(get_pcvar_num(p_webmod)){
		new ip[10],iip[33],eip[33],port[10]
		get_user_ip(id,ip,9)
		get_pcvar_string(p_ip_internal,iip,32)
		get_pcvar_string(p_ip_external,eip,32)
		get_pcvar_string(p_port,port,9)
		if(equal(ip,"192.168.",8))format(url,99,"http://%s:%s/%s",iip,port,file)
		else format(url,99,"http://%s:%s/%s",eip,port,file)
	}
	else if(strlen(url)){
		format(url,99,"%sclimb_scores.html",url)
	}
	return url
}
//Show scoreboard
public climbscores(id){
	if(get_pcvar_num(p_climb)){
		new fpn[100],bool:limit=true,cust_msg[129]
		get_pcvar_string(p_stats_msg,cust_msg,128)
		if(!get_limit())limit=false
		fpn=pathname("climb_scores.html")
		if(get_systime()-score_ts>2){
			new fh=fopen(fpn,"w")
			fprintf(fh,"<html><head>%s<link rel=stylesheet href=http://ian.cammarata.us/climb/sb.css></head><body><table><tr>",limit?"":"<meta http-equiv=pragma content=no-cache>")
			fprintf(fh,"<th height=1%%>Reg</th><th>#</th><th width=28%%>Name</th><th width=15%%>Status</th><th width=8%%>Time</th><th width=6%%>CP#</th><th width=6%%>GC#</th>")
			fprintf(fh,"<th width=8%%>Best</th><th width=6%%>CP#</th><th width=6%%>GC#</th><th width=10%%>Completed</th></tr><tr><td class=st colspan=11></td></tr>")
			//99+144+134=377
			new line[251],name[33],players[32],num,tid,writen_len
			get_players_ordered(players,num)
			for(new i=1;i<=num;i++){
				tid=players[i-1]
				if(!is_user_hltv(tid)&&is_user_connected(tid)){
					get_user_name(tid,name,32)
					htmlspecialchars(name)
					formatex(line,250,"<tr%s><td height=1%>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>",i%2?"":" class='o'",timer[tid][TMR_DBUSER]>0?"y":"n",i,name,getuserstatus(tid),parsetime(getusertime(tid)),timer[tid][TMR_CPSCNT],timer[tid][TMR_GCSCNT],parsetime(timer[tid][TMR_BSTTME]),timer[tid][TMR_BSTGCS],timer[tid][TMR_BSTCPS],timer[tid][TMR_MAPFIN])
					writen_len+=strlen(line)
					if(limit&&writen_len>(1003-strlen(cust_msg)))break
					fprintf(fh,line)
				}
			}
			//1530-150-377=1003
			fprintf(fh,"<tr><td></td></tr><tr><td class=sb colspan=11></td></tr><tr><th colspan=4 height=1%>")
			fprintf(fh,"climb v%s by: Ian Cammarata</th><th colspan=7>%s</th></tr></table></body></html>",VERSION,cust_msg)
			fclose(fh)	
			score_ts=get_systime()
		}
		
		if(!limit)fpn=get_url(id,"climb_scores.html")
		show_motd(id,fpn,"Current Scores")
	}
	return PLUGIN_HANDLED
}
//Show High Scores
public highscores(id){
	new hsurl[150],mapname[33]
	get_pcvar_string(p_stats_hsurl,hsurl,149)
	get_mapname(mapname,32)
	if(strlen(hsurl)){
		formatex(hsurl,149,hsurl,mapname)
		show_motd(id,hsurl,"High Scores")
		return PLUGIN_HANDLED
	}
	if(get_systime()-hscore_ts>10){
		new query[250],data[1]
		data[0]=id
		formatex(query,249,"select p.alias, s.fin_time, s.cps, s.gcs, s.fin_cnt from %sscores s, %splayers p where s.map_name='%s' and p.user_id=s.user_id order by s.fin_time, s.gcs, s.cps limit 20;",db_prefix,db_prefix,mapname)
		SQL_ThreadQuery(db_tuple,"hs_handler",query,data,1)
		hscore_ts=get_systime()
	}
	else{
		new fpn[100],bool:limit=true
		if(!get_limit())limit=false
		fpn=pathname("climb_highscores.html")
		if(!limit)fpn=get_url(id,"climb_highscores.html")
		show_motd(id,fpn,"High Scores")
	}
	
	return PLUGIN_HANDLED
}
public hs_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
   
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	new id=data[0],msg[50],num=SQL_NumResults(query)
	if(!num){
		msg="^x04[Climb] No stats available for the current map."
		return saytext(id,id,msg)
	}
	
	new fpn[100],bool:limit=true,cust_msg[129]
	get_pcvar_string(p_stats_msg,cust_msg,128)
	if(!get_limit())limit=false
	fpn=pathname("climb_highscores.html")
	new fh=fopen(fpn,"w")
	fprintf(fh,"<html><head>%s<link rel=stylesheet href=http://ian.cammarata.us/climb/sb.css></head><body><table><tr>",limit?"":"<meta http-equiv=pragma content=no-cache>")
	fprintf(fh,"<th height=1%%>#</th><th width=45%%>Name</th><th width=20%%>Time</th><th width=10%%>CP#</th>")
	fprintf(fh,"<th width=10%%>GC#</th><th width=10%%>Completed</th></tr><tr><td class=st colspan=6></td></tr>")
	//99+88+92=279
	new name[33],line[151],writen_len
	for(new i=1;i<=num;i++){
		SQL_ReadResult(query,0,name,32)
		htmlspecialchars(name)
		formatex(line,150,"<tr%s><td height=1%>%d</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>",i%2?"":" class='o'",i,name,parsetime(SQL_ReadResult(query,1)),SQL_ReadResult(query,2),SQL_ReadResult(query,3),SQL_ReadResult(query,4))
		writen_len+=strlen(line)
		if(limit&&writen_len>(1101-strlen(cust_msg)))break
		fprintf(fh,line)
		SQL_NextRow(query)
	}
	//1530-150-279=1101
	fprintf(fh,"<tr><td></td></tr><tr><td class=sb colspan=6></td></tr><tr><th colspan=2 height=1%>")
	fprintf(fh,"climb v %s by: Ian Cammarata</th><th colspan=4>%s</th></tr></table></body></html>",VERSION,cust_msg)
	fclose(fh)
	
	if(!limit)fpn=get_url(id,"climb_highscores.html")
	show_motd(id,fpn,"High Scores")
	
	return PLUGIN_HANDLED
}
//Convert seconds to time string with zero padded seconds field
public parsetime(sec){
	new timestr[9],mins
	mins=sec/60
	sec=sec%60
	formatex(timestr,8,"%d:%s%d",mins,sec<10?"0":"",sec)
	return timestr
}
//Calculate client climb time in seconds
public getusertime(id){
	new ptime,cflags=timer[id][TMR_CFLAGS]
	//ptime=timer[id][TMR_FINISH]-timer[id][TMR_STARTD]
	//if(cflags&CF_STOP&&timer[id][TMR_SESFIN]>0)ptime=ptime<10000?ptime:0
	if(cflags&CF_STOP&&timer[id][TMR_SESFIN]>0)ptime=timer[id][TMR_FINISH]-timer[id][TMR_STARTD]
	else if(cflags&CF_STOP)ptime=0
	else if(cflags&CF_START)ptime=get_systime()-timer[id][TMR_STARTD]
	else if(cflags&CF_PAUSE)ptime=timer[id][TMR_STARTD]
	return ptime
}
//Set clock on HUD to current climb time
public hudtime(){
	if(get_pcvar_num(p_climb)){
		new player,players[32],num
		get_players(players,num,"ac")
		for(new i=0;i<num;i++){
			player=players[i]
			message_begin(1,get_user_msgid("RoundTime"),{0,0,0},player)
			write_short(getusertime(player)+1)
			message_end()
			if(timer[player][TMR_CFLAGS]&CF_PAUSE)clmsg(player,"PAUSED")
		}
	}
	return 1
}
//Return string describing user status
public getuserstatus(id){
	new msg[12]
	if(timer[id][TMR_CFLAGS]&CF_STOP&&timer[id][TMR_SESFIN]>0)msg="Finished"
	/*else switch(timer[id][TMR_CFLAGS]){
		case TMR_CFLAGS_STOP:	msg="Not Started"
		case TMR_CFLAGS_STRT: msg="Climbing"
		case TMR_CFLAGS_PAUS: msg="Paused"
	}*/
	else if(timer[id][TMR_CFLAGS]&CF_STOP)msg="Not Started"
	else if(timer[id][TMR_CFLAGS]&CF_START)msg="Climbing"
	else if(timer[id][TMR_CFLAGS]&CF_PAUSE)msg="Paused"
	return msg
}
//Show Hud Message
public clmsg(id, msg[]){
	set_hudmessage(get_pcvar_num(p_msg_r),get_pcvar_num(p_msg_g),get_pcvar_num(p_msg_b),get_pcvar_float(p_msg_x),get_pcvar_float(p_msg_y),0,0.0,5.0,0.5,0.5,4)
	show_hudmessage(id,msg)
	return PLUGIN_HANDLED
}
//Make client solid
public solid(id){
	set_user_rendering(id,kRenderFxNone,0,0,0,kRenderNormal,255)
	entity_set_int(id,EV_INT_solid,SOLID_BBOX)
	return PLUGIN_CONTINUE
}
//Make client unsolid
public unsolid(id){
	if(get_pcvar_num(p_render))set_user_rendering(id,kRenderFxPulseSlow,0,0,0,kRenderTransTexture,50)
	else set_user_rendering(id,kRenderFxHologram,0,0,0,kRenderTransAdd,0)
	entity_set_int(id,EV_INT_solid,SOLID_NOT)
	return PLUGIN_CONTINUE
}
//Check client solid
public playersolid(id){
	if(entity_get_int(id,EV_INT_solid)==SOLID_BBOX)return 1
	return 0
}
//Use in IF statements to automatically print error if false 
public isalive(id){
	if(is_user_alive(id))return 1
	clmsg(id,"You must be alive to execute this command.")
	return 0
}
//Use in IF statements to automatically print error if false
public notpaused(id){
	if(!(timer[id][TMR_CFLAGS]&CF_PAUSE))return 1
	clmsg(id,"You can't execute this command while paused.")
	return 0
}
//Used in IF statements a lot
public isct(id){
	if(get_user_team(id)==2)return 1
	return 0
}
//Called when client takes damage
public damage(id){
	new ida[1]
	ida[0]=id
	set_task(0.1,"damage_handle",_,ida,1)
}
//Damage hander, delayed to provide HUD consistency
public damage_handle(ida[1]){
	new id=ida[0]
	if(get_pcvar_num(p_climb)&&is_user_alive(id)){
		new chp=get_user_health(id)
		if(chp!=hp){
			new wpn,atk=get_user_attacker(id,wpn)
		//Heal hp immediately
			if((atk==0||(id==atk&&wpn==0))&&!task_exists(50+id))set_user_health(id,hp)
		//Heal delayed
			else{
				if(task_exists(50+id))remove_task(50+id)
				set_task(5.0,"heal",50+id,ida,1)
			//Fix hp display at 0
				if(chp==256)set_user_health(id,257)
			}
		}
		else if(task_exists(50+id))remove_task(50+id)
	}
	return PLUGIN_HANDLED
}
//Heal client to predetermined HP required by map
public heal(ida[]){
	if(is_user_alive(ida[0]))set_user_health(ida[0],hp)
	return PLUGIN_HANDLED
}
public pfn_keyvalue(ent){
	static bool:first_run=true
	new temp_ent
	if(first_run){
		for(new i=0;i<32;i++){
			temp_ent=create_entity("info_player_start")
			if(temp_ent>0){
				dyn_spawn_count++
				dyn_spawn_ids[dyn_spawn_count]=temp_ent
				entity_set_int(temp_ent,EV_INT_iuser1,1)
				//DispatchKeyValue(temp_ent,"origin","0 0 0")
				//DispatchKeyValue(temp_ent,"angles","0 0 0")
			}
		}
		first_run=false
	}	
	/*static bool:got_first=false,bool:done=false,first_id,first_orig[21],first_angle[21]
	new class[26],key[26],val[26]
	copy_keyvalue(class,25,key,25,val,25)

	if(equal(class,"info_player_start")&&is_valid_ent(ent)){
		if(!done){
			if(!got_first){
				console_print(0,"DEBUG: Got First")
				first_id=ent
				got_first=true
			}	
			if(got_first&&!done&&ent==first_id){
				if(equal(key,"origin"))copy(first_orig,20,val)
				else if(equal(key,"angles"))copy(first_angle,20,val)
				if(strlen(first_orig)&&strlen(first_angle)){
					done=true
					console_print(0,"DEBUG: orig:%s angle:%s",first_orig,first_angle)
					new i,sp
					for(i=0;i<31;i++){
						sp=create_entity("info_player_start")
						if(ent>0){
							entity_set_int(sp,EV_INT_iuser1,1)
							DispatchKeyValue(sp,"origin",first_orig)
							DispatchKeyValue(sp,"angles",first_angle)
						}
					}
				}
			}
		}
		if(done&&entity_get_int(ent,EV_INT_iuser1)!=1)remove_entity(ent)
	}*/
	return PLUGIN_CONTINUE
}
public plugin_cfg(){
//fix timer built into map
	set_cvar_num("sv_restartround",1)
	set_cvar_num("sv_gravity",800)
	new ent,count=0,tmpstr[32],Float:tmpflt
	get_mapname(tmpstr,32)
//map specific fixes : real gravity in nasa maps instead of trigger_push brush.
	if(equali(tmpstr,"kz_man_nasa")||equali(tmpstr,"kz_man_eznasa")){
		remove_entity_name("trigger_push")
		set_cvar_num("sv_gravity",500)
	}
//Non-Solid check: *trigger_teleport,trigger_once,trigger_gravity,trigger_push
//trigger_teleport
	ent=find_ent_by_class(-1,"trigger_teleport")
	while(ent>0){
		trig[trignum]=ent
		trignum++
		ent=find_ent_by_class(ent,"trigger_teleport")
	}
//trigger_gravity
	ent=find_ent_by_class(-1,"trigger_gravity")
	while(ent>0){
		trig[trignum]=ent
		trignum++
		ent=find_ent_by_class(ent,"trigger_gravity")
	}
//trigger_once
/*	ent=find_ent_by_class(-1,"trigger_once")
	while(ent>0){
		trig[trignum]=ent
		trignum++
		ent=find_ent_by_class(ent,"trigger_once")
	}
//trigger_push
	ent=find_ent_by_class(-1,"trigger_push")
	while(ent>0){
		trig[trignum]=ent
		trignum++
		ent=find_ent_by_class(ent,"trigger_push")
	}*/
	//server_print("***** %d trigger entities found *****",trignum)
//Remove neg dmg: *trigger_hurt,*func_door
//func_door neg dmg removal
	ent=find_ent_by_class(-1,"func_door")
	while(ent>0){
		tmpflt=entity_get_float(ent,EV_FL_dmg)
		if(tmpflt<0){
			count++
			hp=floatround(tmpflt)
			remove_entity(ent)
			ent=-1
		}
		ent=find_ent_by_class(ent,"func_door")
	}
	server_print("***** %d neg dmg func_door entities removed *****",count)
//trigger_hurt neg dmg removal
	count=0
	ent=find_ent_by_class(-1,"trigger_hurt")
	while(ent>0){
		tmpflt=entity_get_float(ent,EV_FL_dmg)
		if(tmpflt<0){
			count++
			hp=floatround(tmpflt)
			remove_entity(ent)
			ent=-1
		}
		ent=find_ent_by_class(ent,"trigger_hurt")
	}
	server_print("***** %d neg dmg trigger_hurt entities removed *****",count)
	hp=abs(hp)
	hp=clamp(hp,100,511)
//Healing door trigger button removal
	count=0
	ent=find_ent_by_class(-1,"func_button")
	while(ent>0){
		entity_get_string(ent,EV_SZ_target,tmpstr,32)
		if(containi(tmpstr,"heal")>-1||containi(tmpstr,"goddoor")>-1){
			count++
			remove_entity(ent)
			ent=-1
		}
		ent=find_ent_by_class(ent,"func_button")
	}
	server_print("***** %d healing door buttons entities removed *****",count)
//func_breakables
	ent=find_ent_by_class(-1,"func_breakable")
	while(ent>0){
		if(entity_get_int(ent,EV_INT_spawnflags)==2){
			brk[brknum]=ent
			brknum++
		}
		ent=find_ent_by_class(ent,"func_breakable")
	}
	server_print("***** %d func_breakable entities found *****",brknum)
//Remove unneeded entities if it's a climbing map.
	count=0
	new bool:has_start_button=false,Float:sb_orig[3]
	ent=find_ent_by_class(-1,"func_button")
	while(ent>0){
		entity_get_string(ent,EV_SZ_target,tmpstr,32)
		if(equal(tmpstr,"counter_start")||equal(tmpstr,"clockstartbutton")||equal(tmpstr,"firsttimerelay")){
			has_start_button=true
			get_brush_entity_origin(ent,sb_orig)
			break
		}
		ent=find_ent_by_class(ent,"func_button")
	}
	if(has_start_button){
		remove_entity_name("player_weaponstrip")
		remove_entity_name("armoury_entity")
		remove_entity_name("info_player_deathmatch")
		get_mapname(tmpstr,32)
	//Health chargers needed for some maps as climbing platforms, don't remove
		//if(!(equali(tmpstr,"kz_real_skyscraper")||equali(tmpstr,"kz_northpole_b01")))remove_entities("func_healthcharger")
		count++
	//Remove map built in start button sounds
		ent=find_ent_by_class(-1,"ambient_generic")
		while(ent>0){
			entity_get_string(ent,EV_SZ_targetname,tmpstr,32)
			if(equal(tmpstr,"counter_start")||equal(tmpstr,"clockstartbutton")||equal(tmpstr,"firsttimerelay")){
				remove_entity(ent)
			}
			ent=find_ent_by_class(ent,"ambient_generic")
		}
	//Count original map spawns and remove extra dyn created spawns
		new map_spawns[32][2],map_spawns_count,Float:orig[3]
		ent=find_ent_by_class(-1,"info_player_start")
		while(ent>0&&map_spawns_count<32){
			if(entity_get_int(ent,EV_INT_iuser1)!=1){
				entity_get_vector(ent,EV_VEC_origin,orig)
				map_spawns[map_spawns_count][0]=floatround(vector_distance(orig,sb_orig))
				map_spawns[map_spawns_count][1]=ent
				map_spawns_count++
				while(dyn_spawn_count>=0){
					entity_get_string(dyn_spawn_ids[dyn_spawn_count],EV_SZ_classname,tmpstr,31)
					if(equal(tmpstr,"info_player_start")&&entity_get_int(dyn_spawn_ids[dyn_spawn_count],EV_INT_iuser1)==1){
						remove_entity(dyn_spawn_ids[dyn_spawn_count])
						dyn_spawn_count--
						break
					}
					else dyn_spawn_count--
				}
			}
			ent=find_ent_by_class(ent,"info_player_start")
		}
	//Save origin of spawn closest to start and arrange all spawn points to positions of the next 4 closest
		//SortCustom2D(map_spawns,map_spawns_count,"spawn_distance_sort");
		SortStrings(map_spawns,map_spawns_count)
		entity_get_vector(map_spawns[0][1],EV_VEC_origin,spawn_tp_orig)
		//remove_entity(map_spawns[0][1])
		entity_set_vector(map_spawns[0][1],EV_VEC_origin,Float:{0,0,0})
		for(new i=5;i<map_spawns_count;i++)//normal i=5
			entity_set_vector(map_spawns[i][1],EV_VEC_origin,Float:{0,0,0})
		/*new Float:orig2[3],i
		entity_get_vector(map_spawns[1][1],EV_VEC_origin,orig)
		entity_get_vector(map_spawns[2][1],EV_VEC_origin,orig2)
		for(i=3;i<map_spawns_count;i++){
			if((i/2)==(i%2))entity_set_vector(map_spawns[i][1],EV_VEC_origin,orig)
			else entity_set_vector(map_spawns[i][1],EV_VEC_origin,orig2)
		}*/
	}
	else{
		while(dyn_spawn_count>=0){
			ent=dyn_spawn_ids[dyn_spawn_count]
			entity_get_string(ent,EV_SZ_classname,tmpstr,31)
			//entity_get_string(dyn_spawn_ids[i],EV_SZ_classname,tmpstr,31)
			if(equal(tmpstr,"info_player_start")&&entity_get_int(ent,EV_INT_iuser1)==1)remove_entity(ent)
			dyn_spawn_count--
		}
	}
	return PLUGIN_CONTINUE
}
/*public spawn_distance_sort(elem1[],elem2[]){
	if(elem1[1]<elem2[1])return -1
	if(elem1[1]>elem2[1])return 1
	return 0
}*/
//Future use, to block hook, or detect hook cheaters a.k.a. hookers
public phook(id){
	hooked[id]=1
	return PLUGIN_CONTINUE
}
public mhook(id){
	hooked[id]=0
	return PLUGIN_CONTINUE
}
//Make a public viewable help page for this on my web host. 
public climb_help(id){
	if(get_pcvar_num(p_climb)){
		show_motd(id,"http://ian.cammarata.us/climb/help/2a2/","Climb Plugin Help")
	}
	return PLUGIN_HANDLED
}
public boost_help(id){
	if(get_pcvar_num(p_climb)){
		show_motd(id,"http://ian.cammarata.us/climb/help/2a2/boost.html","Climb Plugin Help")
	}
	return PLUGIN_HANDLED
}
//Force client to CT
public menuteam(id){
	if(get_pcvar_num(p_climb))client_cmd(id,"slot2")
	return PLUGIN_CONTINUE
}
//Force client to choose random model
public menuclass(id){
	if(get_pcvar_num(p_climb)){
		client_cmd(id,"slot5")
	}
	return PLUGIN_CONTINUE
}
//Used to teleport clients, prevents client collisions and getting stuck
public teleport(id,Float:orig[3]){
	new Float:c2[3],player,players[32],num
	get_players(players,num,"ac")
	unsolid(id)
	for(new i=0;i<num;i++){
		player=players[i]
		if(id!=player){
			entity_get_vector(player,EV_VEC_origin,c2)
			if(vector_distance(orig,c2)<74)unsolid(player)
		}
	}
	entity_set_vector(id,EV_VEC_velocity,Float:{0.0, 0.0, 0.0})
	entity_set_vector(id,EV_VEC_origin,orig)
	delay_duck(id)
	return 1
}
//Used by teleport and traceline-timer start, prevents getting stuck
public delay_duck(id){
	new ida[1]
	ida[0]=id
	set_task(0.01,"force_duck",_,ida,1)
	set_entity_flags(ida[0],FL_DUCKING,1)
}
//Task for delay_duck 
public force_duck(ida[1]){
	set_entity_flags(ida[0],FL_DUCKING,1)
}
//Use in IF statements to automatically print error if false
public cvar_enabled(id,p_cvar){
	if(get_pcvar_num(p_cvar)==0){
		clmsg(id,"This command is disabled.")
		client_print(id,print_chat,"This command has been disabled by the server administrator.")
		return 0
	}
	return 1
}
//Admin command, teleport to client
public goto_player(id,level,cid){
	if(cmd_access(id,level,cid,2)&&get_pcvar_num(p_climb)){
		new tid,arg[24]
		read_argv(1,arg,sizeof(arg)-1)
		tid=cmd_target(tid,arg,4)
		if(tid){
			new Float:orig[3]
			entity_get_vector(tid,EV_VEC_origin,orig)
			teleport(id,orig)
		}
	}
	return PLUGIN_HANDLED
}
//Move yourself to spectator
public spectate(id){
	if(get_pcvar_num(p_climb)&&(get_user_flags(id)&ADMIN_RESERVATION?1:(cvar_enabled(id,p_allow_spectators)))){
		cs_set_user_team(id,3)
		set_msg_block(get_user_msgid("DeathMsg"),1)
		user_kill(id,1)
	}
	return PLUGIN_HANDLED
}
//Block client kill command
public client_kill(id){
	if(get_pcvar_num(p_climb)==1)return PLUGIN_HANDLED
	return PLUGIN_CONTINUE
}
//Used to block some commands
public block_cmd(id){
	if(get_pcvar_num(p_climb))return PLUGIN_HANDLED
	return PLUGIN_CONTINUE
}
public block_cmd2(id){
	return PLUGIN_HANDLED
}
//Block client trying to switch teams
public block_jointeam(id){
	if(get_pcvar_num(p_climb)&&isct(id))return PLUGIN_HANDLED
	return PLUGIN_CONTINUE
}
//Forward to catch all formats for commands
public client_command(id){
	if(!get_pcvar_num(p_climb))
		return PLUGIN_CONTINUE
	new cmd[21]
	read_argv(0,cmd,20)
//If say command replace command with say text, abort if more than one word
	if(equal(cmd,"say")||equal(cmd,"say_team")){
		read_argv(1,cmd,20)
		trim(cmd)
		if(contain(cmd," ")>-1)return PLUGIN_CONTINUE
	}
//Remove slashes
	if(equal(cmd,"/",1)||equal(cmd,"\",1)||equal(cmd,".",1)||equal(cmd,"!",1))copy(cmd,20,cmd[1])
//Make a checkpoint
	if(equali(cmd,"checkpoint")||equali(cmd,"check")||equali(cmd,"cp"))check(id)
//Go to checkpoint
	else if(equali(cmd,"gocheck")||equali(cmd,"gc")||equali(cmd,"tp")||equali(cmd,"tele"))gocheck(id)
//Boost help page
	else if(equali(cmd,"boost"))boost_help(id)
//Solid Boost
	else if(equali(cmd,"solid")||equali(cmd,"semiclip"))change_boost(id,CF_SOLID)
//Jump Boost
	else if(equali(cmd,"superjump")||equali(cmd,"sjump")||equali(cmd,"sj")||equali(cmd,"longjump")||equali(cmd,"ljump")||equali(cmd,"lj"))change_boost(id,CF_SUPER_JUMP)
//Double Jump Boost
	else if(equali(cmd,"doublejump")||equali(cmd,"djump")||equali(cmd,"dj"))change_boost(id,CF_DOUBLE_JUMP)
//Stuck
	else if(equali(cmd,"stuck")||equali(cmd,"unstuck"))stuck(id)
//Pause
	else if(equali(cmd,"pause")||equali(cmd,"unpause"))change_status(id,CF_PAUSE)
//Restart
	else if(equali(cmd,"restart")||equali(cmd,"reset"))reset(id)
//Stop
	else if(equali(cmd,"stop"))stop(id)
//Time/Scores
	else if(equali(cmd,"scoreboard")||equali(cmd,"score")||equali(cmd,"scores"))climbscores(id)
//Top Scores
	else if(equali(cmd,"top10")||equali(cmd,"top15")||equali(cmd,"top")||equali(cmd,"highscores")||equali(cmd,"best")||equali(cmd,"rank"))highscores(id)
//Respawn
	else if(equali(cmd,"respawn")||equali(cmd,"spawn"))frespawn(id)
//Spectate
	else if(equali(cmd,"spectate")||equali(cmd,"spec"))spectate(id)
//Climb Help
	else if(equali(cmd,"help")||equali(cmd,"climbhelp")||equali(cmd,"kzhelp"))climb_help(id)
//If none of above conditions met, don't block the command
	else return PLUGIN_CONTINUE
	return PLUGIN_HANDLED
}
public donothing(){
	return PLUGIN_CONTINUE
}
public regwarn(id){
	if(timer[id][TMR_DBUSER]<1){
		saytext(id,id,"^x04You must register/login for your stats to save. Say /help for more info.")
		return 1
	}
	return 0
}
public db_init(){
	//update from old db version
	//alter table climb_scores add boosts int
	//alter table climb_scores add wpns int
	//alter table climb_scores add score int
	//update climb_scores set boosts=-1
	//update climb_scores set wpns=-1
	static host[32],user[32],pass[32],name[32],type[12]
	
	get_cvar_string("climb_db_host",host,31)
	get_cvar_string("climb_db_user",user,31)
	get_cvar_string("climb_db_pass",pass,31)
	get_cvar_string("climb_db_name",name,31)
	get_cvar_string("climb_db_type",type,11)
	get_cvar_string("climb_db_prefix",db_prefix,31)

	//SQL_SetAffinity(type)
	db_tuple=SQL_MakeDbTuple(host,user,pass,name)
	
	new query[600]
	formatex(query,599,"\
			create table %splayers (\
				user_id integer primary key autoincrement,\
				steam_id char(25) unique,\
				password char(6),\
				user_name varchar(20) unique,\
				alias varchar(32) unique,\
				email varchar(50) unique,\
				cflags integer);\
	",db_prefix)
	SQL_ThreadQuery(db_tuple,"db_generic_handler",query)
	formatex(query,599,"\
			create table %sscores (\
				score_id integer primary key autoincrement,\
				score integer,\
				server_ip char(15),\
				user_id integer,\
				map_name varchar(32),\
				fin_time integer,\
				cps integer,\
				gcs integer,\
				fin_cnt integer,\
				boosts integer,\
				wpns integer,\
				server_time_stamp integer);\
			create unique index scores_usermap_idx on %sscores (user_id, map_name);\
			create index scores_score on %sscores (score);\
	",db_prefix,db_prefix,db_prefix)
	SQL_ThreadQuery(db_tuple,"db_generic_handler",query)
	formatex(query,599,"\
			create table %ssessions (\
				steam_id char(25) primary key,\
				user_id integer unique);\
	",db_prefix)
	SQL_ThreadQuery(db_tuple,"db_generic_handler",query)
	return PLUGIN_HANDLED
}
public db_generic_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
   
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	return PLUGIN_HANDLED
}
public logout(id){
	for(new i=0;i<8;i++)origins[id][i]=0.0
	for(new i=0;i<11;i++)timer[id][i]=0
}
public login(id){
	if(get_pcvar_num(p_climb)){
		if(climb_save){
			if(timer[id][TMR_DBUSER]>0){//Already logged in
				client_print(id,print_console,"[Climb] Login Error: You are already logged in.")
				return PLUGIN_HANDLED
			}
			new query[99],data[28]
			data[0]=id
			if(read_argc()>1){//Client is logging in with a user/pass
				new user[21],pass[21]
				read_argv(1,user,20)
				read_argv(2,pass,20)
				rotwtf(pass,6)
				data[1]=1
				formatex(query,98,"select user_id from %splayers where user_id='%s' and password='%s';",db_prefix,user,pass)
				formatex(data[2],25,user)
				SQL_ThreadQuery(db_tuple,"login_handler",query,data,28)
			}
			else{//Client is logging in with SteamID
				new sid[26]
				get_user_authid(id,sid,25)
				data[1]=2
				formatex(query,98,"select user_id, password from %splayers where steam_id='%s';",db_prefix,sid)
				formatex(data[2],25,sid)
				SQL_ThreadQuery(db_tuple,"login_handler",query,data,28)
			}
		}
		else client_print(id,print_console,"[Climb] Login Error: Can't Login; Stats not enabled.")
	}
	return PLUGIN_HANDLED
}
public login_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	new id=data[0]
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
  
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	if(SQL_NumResults(query)<1)
		return client_print(id,print_console,"[Climb] Login Error: Not a valid account.")
	
	if(data[1]==2){
		new pass[7]
		SQL_ReadResult(query,1,pass,6)
		if(equali(pass,"shared")){
			client_print(id,print_console,"[Climb] Login Error: You are using a shared SteamID.  Please login with a username and password.")
			return PLUGIN_HANDLED
		}
	}
	timer[id][TMR_DBUSER]=SQL_ReadResult(query,0)
	new msg[100]
	formatex(msg,99,"[Climb] Login: Success - Account: %s",data[2])
	client_print(id,print_console,msg)
	format(msg,99,"^x04%s",msg)
	saytext(id,id,msg)
	db_load(id)
	return PLUGIN_HANDLED
}
public db_load(id){
	new query[150],mapname[33],data[1]
	data[0]=id
	get_mapname(mapname,32)
	formatex(query,149,"select fin_time, cps, gcs, fin_cnt from %sscores where user_id=%d and map_name='%s';",db_prefix,timer[id][TMR_DBUSER],mapname)
	SQL_ThreadQuery(db_tuple,"db_load_handler",query,data,1)
	return PLUGIN_HANDLED
}
public db_load_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
  
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	new msg[100],id=data[0]
	if(!SQL_NumResults(query))
		msg="^x04No stats available for this account on the current map."
	else{
		new mapname[33],timestr[9]
		get_mapname(mapname,32)
		timer[id][TMR_BSTTME]=SQL_ReadResult(query,0)
		timer[id][TMR_BSTCPS]=SQL_ReadResult(query,1)
		timer[id][TMR_BSTGCS]=SQL_ReadResult(query,2)
		timer[id][TMR_MAPFIN]=SQL_ReadResult(query,3)
		timestr=parsetime(timer[id][TMR_BSTTME])
		formatex(msg,99,"^x04Stats loaded for %s - %s^t(%d CPS/ %d GCS)^tCompleted %d",mapname,timestr,timer[id][TMR_BSTCPS],timer[id][TMR_BSTGCS],timer[id][TMR_MAPFIN])
	}
	saytext(id,id,msg)
	return PLUGIN_HANDLED
}
public db_save(id){
	new query[150],name[33]
	get_mapname(name,32)
	if(timer[id][TMR_MAPFIN]==1)
		formatex(query,149,"insert into %sscores (user_id, map_name, fin_time, cps, gcs, fin_cnt)\
		 																	values (%d, '%s', %d, %d, %d, %d);\
		",db_prefix,timer[id][TMR_DBUSER],name,timer[id][TMR_BSTTME],timer[id][TMR_BSTCPS],timer[id][TMR_BSTGCS],timer[id][TMR_MAPFIN])
	else
		formatex(query,149,"update %sscores set fin_time=%d, cps=%d, gcs=%d, fin_cnt=%d where user_id=%d and map_name='%s';",db_prefix,timer[id][TMR_BSTTME],timer[id][TMR_BSTCPS],timer[id][TMR_BSTGCS],timer[id][TMR_MAPFIN],timer[id][TMR_DBUSER],name)
	SQL_ThreadQuery(db_tuple,"db_save_handler",query)
	get_user_name(id,name,32)
	formatex(query,149,"update %splayers set alias='%s' where user_id=%d;",db_prefix,name,timer[id][TMR_DBUSER])
	SQL_ThreadQuery(db_tuple,"db_save_handler",query)
	return PLUGIN_HANDLED
}
public db_save_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
  
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	return PLUGIN_HANDLED
}
public reg(id){
	if(get_pcvar_num(p_climb)){
		if(climb_save){
			new query[100],sid[26],name[33],data[43]
			data[0]=id
			data[1]=0
			get_user_authid(id,sid,25)
			get_user_name(id,name,32)
			//Register user/pass
			if(read_argc()>1){
				if(read_argc()!=3){
					client_print(id,print_console,"[Climb] Registration Error: Invalid number of arguments")
					return PLUGIN_HANDLED
				}
				new user[21],pass[50]
				//Read password and check length
				read_argv(2,pass,20)
				if(strlen(pass)<10){
					client_print(id,print_console,"[Climb] Registration Error: Password must be at least 10 characters.")
					return PLUGIN_HANDLED
				}
				//Read user
				read_argv(1,user,20)
				//Store user/pass in data array to pass to handler for autologin
				formatex(data[2],20,user)
				formatex(data[22],20,pass)
				//Create password hash
				rotwtf(pass,6)
				//Register shared SteamID
				formatex(query,99,"insert into %splayers (steam_id,password) values ('%s','shared')",db_prefix,sid)
				SQL_ThreadQuery(db_tuple,"reg_handler",query,data,43)
				//Register user/pass 
				data[1]=1
				formatex(query,99,"insert into %splayers (user_name,password,alias) values ('%s','%s','%s')",db_prefix,user,pass,name)
				SQL_ThreadQuery(db_tuple,"reg_handler",query,data,43)
			}
			//Else register SteamID
			else{
				data[1]=2
				formatex(query,99,"insert into %splayers (steam_id,alias) values ('%s','%s')",db_prefix,sid,name)
				SQL_ThreadQuery(db_tuple,"reg_handler",query,data,43)
			}
		}
		else client_print(id,print_console,"[Climb] Registration Error: Can't Register; Stats not enabled.")
	}
	return PLUGIN_HANDLED
}
public reg_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	new id=data[0],flag=data[1],user[21],pass[21]
	format(user,20,data[2])
	format(pass,20,data[22])
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("[Climb] Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("[Climb] Query failed: %s",error)
   
	if(errnum){
		client_print(id,print_console,"[Climb] Registration Error: Database error, please notify the server admin.")
		return server_print("[Climb] Query Error: %s",error)
	}
	
	
	if(flag==0)client_print(id,print_console,"[Climb] Recorded shared SteamID.")
	else{
		client_print(id,print_console,"[Climb] Registration Successful.")
		new cmd[50]
		formatex(cmd,49,"login %s %s",user,pass)
		client_cmd(id,cmd)
	}
	return PLUGIN_HANDLED
}
public rotwtf(string[],out_len){
	new len=strlen(string),str[99],cnt=0,tok=len-1
	copy(str,out_len,string)
	for(new index=0;index<11;index++){
		if('a'<=string[index]<='z')
			str[index]=(str[index]-'a'+string[tok]+index)
		else if('A'<=str[index]<='Z')
			str[index]=(str[index]-'A'+string[tok]+index)
		else if('0'<=str[index]<='9')
			str[index]=(str[index]-'0'+string[tok]+index)
		switch(cnt){
			case 0:str[index]=str[index]%26+'a'
			case 1:str[index]=str[index]%26+'A'
			case 2:str[index]=str[index]%10+'0'
		}
		tok--
		if(tok<0)tok=len-1
		cnt++
		if(cnt==3)cnt=0
	}
	copy(string,out_len,str)
	return PLUGIN_HANDLED
}
public plugin_init(){
	register_plugin("Climb",VERSION,"Ian Cammarata")
	register_cvar("climb_version",VERSION,FCVAR_SERVER)
	
	p_climb=  register_cvar("climb","1",FCVAR_SERVER)
	p_boost=  register_cvar("climb_boost","1")
	p_cpprice=register_cvar("climb_cpprice","0")
	
	register_cvar("climb_save","1")
	
	register_cvar("climb_db_type","sqlite")
	register_cvar("climb_db_host","127.0.0.1")
	register_cvar("climb_db_user","")
	register_cvar("climb_db_pass","")
	register_cvar("climb_db_name","climb")
	register_cvar("climb_db_prefix","climb_")
	
	p_msg_r=register_cvar("climb_msg_r","0")
	p_msg_g=register_cvar("climb_msg_g","150")
	p_msg_b=register_cvar("climb_msg_b","250")
	p_msg_x=register_cvar("climb_msg_x","0.05")
	p_msg_y=register_cvar("climb_msg_y","0.5")
	
	p_sounds=register_cvar("climb_sounds","1")
	p_render=register_cvar("climb_unsolid_type","0")
	
	p_ip_internal=register_cvar("ip_internal","localhost")
	p_ip_external=register_cvar("ip_external","localhost")
	p_port=get_cvar_pointer("port")
	
	p_webmod=register_cvar("climb_webmod","0")
	p_stats_path=  register_cvar("climb_stats_path","")
	p_stats_url=   register_cvar("climb_stats_url","")
	p_stats_hsurl= register_cvar("climb_stats_hsurl","") //Use %s in place of map name
	p_stats_msg=   register_cvar("climb_stats_msg","")
	
	p_allow_spectators=get_cvar_pointer("allow_spectators")
	p_startmoney=  register_cvar("climb_startmoney","1337")
//General Client Commands
//These commands get blocked always.
	register_clcmd("fullupdate","block_cmd2")
//These commands get blocked when climb is enabled.
	register_clcmd("chooseteam","block_cmd")
	register_clcmd("buy","block_cmd")
	register_clcmd("buyammo1","block_cmd")
	register_clcmd("buyammo2","block_cmd")
	register_clcmd("buyequip","block_cmd")
	register_clcmd("jointeam","block_jointeam")
//Commands to detect cheats.
	register_clcmd("+hook","phook")
	register_clcmd("+rope","phook")
	register_clcmd("-hook","mhook")
	register_clcmd("-rope","mhook")
//Commands referencing function 'donothing' are picked up by the more flexible code in the client_command forward.
	register_clcmd("climbhelp","donothing",0,"- Veiw climb help.")
	register_clcmd("cp","donothing",_,"- Make a checkpoint")
	register_clcmd("gc","donothing",_,"- Teleport to last checkpoint")
	register_clcmd("stuck","donothing",_,"- Teleport to previous checkpoint")
	register_clcmd("restart","donothing",_,"- Stop and respawn.")
	register_clcmd("stop","donothing",_,"- End current climbing run.")
	register_clcmd("pause","donothing",_,"- Pause yourself.")
	register_clcmd("scoreboard","donothing",_,"- View score board.")
	register_clcmd("respawn","donothing",_,"- Force respawn")
	register_clcmd("boost","donothing",_,"- Boost.")
	register_clcmd("spec","donothing",_,"- Spectate mode.")
//Commands related to stats account system.
	register_clcmd("register","reg",_,"- (Console Only) Register for stats tracking.")
	register_clcmd("login","login",_,"- (Console Only) Login to stats account.")
//Admin Commands
	register_clcmd("amx_goto","goto_player",ADMIN_SLAY)
//For scoreboard backend
	//register_srvcmd("amx_climb_sbrefresh","htmlscoreboard")
//Events
	register_event("DeathMsg","death_msg","a")
	register_event("ResetHUD","spawned","b")
	//register_event("Damage","damage","b")
	register_event("Health","damage","b")
	register_event("ShowMenu","menuclass","b","4&CT_Select","4&Terrorist_Select")
	register_event("ShowMenu","menuteam","b","4&Team_Select_Spect","4&Team_Select","4&IG_Team_Select")
//Init DB
	if(get_cvar_num("climb_save"))climb_save=true
	if(climb_save)db_init()
//Init anti flood time stamps
	score_ts=get_systime()
	hscore_ts=get_systime()
//Task to update time on clients HUD; Task set for half second to minimize flickering of the timer.
	set_task(0.5,"hudtime",_,_,_,"b")
//Register FakeMeta Traceline forward.
	register_forward(FM_TraceLine, "traceline", 1)
//Load Start/Finish Commands
	new ini[50]
	get_configsdir(ini,49)
	format(ini,49,"%s/climb.ini",ini)
	if(file_exists(ini)){
		new line=0,text[50],len
		while(read_file(ini,line++,text,sizeof(text)-1,len)){
			if(!equal(text,";",1)&&!equal(text,"#",1)&&!equal(text,"//",2)){
				if(sfcount<SF_MAX){
					sfactions[sfcount]=text
					sfcount++
				}
			}
		}
	}
	return PLUGIN_CONTINUE
}
public plugin_end()
	if(climb_save)
		SQL_FreeHandle(db_tuple)