[Half-Life AMXX] / climb.sma Repository:
ViewVC logotype

View of /climb.sma

Parent Directory Parent Directory | Revision Log Revision Log


Revision 32 - (download) (annotate)
Sat Nov 24 23:45:42 2007 UTC (16 years, 4 months ago) by ian
File size: 121168 byte(s)
removed mpbhops functionality - will maintain separately,
fixed db load functions - not handling float correctly & not loading best score into memory,
fixed highscores - not reading float from db
/*
Climb v2.0a4
Copyright (C) 2006-2007 Ian (Juan) Cammarata

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------

http://ian.cammarata.us
Sep 21 18:27


To do:
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
Recode custom flashlight without temp entities


Description:
This plugin is designed for use in climbing maps like those available from www.kreedz.com.


Commands:
say /checkpoint : Save your position.
say /gocheck : Teleport you to your last saved position.
say /ungocheck : Undo a gocheck in case you accidentally use one after you make a tough jump.
say /stuck : Teleport you to your previous checkpoint in case you get stuck in a wall.
amx_goto : Teleport to another player. (Can't be used while timer is running)

A full help page can be found at http://ian.cammarata.us/projects/climb/help/2a3/en/commands


Cvars:
climb <1|0> : Enables|Disables all plugin functionality.  The plugin now enables and disables itself, you should only use this cvar to turn the plugin on in maps which don't have a start button.
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_start_respawn <0|1> : When enabled, eliminates unnecessary respawning. (Defaults to off)
climb_water_nodraw: <0|1> : When enabled makes func_water entities invisible to help with players FPS issues. (Defaults to off)

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 WebMod's 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.

climb_light_r <255|0-255> : Sets the red value for the custom flashlight's color.
climb_light_g <255|0-255> : Sets the green value for the custom flashlight's color.
climb_light_b <255|0-255> : Sets the blue value for the custom flashlight's color.

*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:
AMXX 1.76 or higher
Engine module
Fakemeta module
DB Module (MySQL or SQLite/Only needed if using CLIMB_SAVE 1.)


Notes:
Make sure you place the climb.txt file in addons\amxmodx\data\lang

I highly recommend using All Chat along with this plugin:
http://forums.alliedmods.net/showthread.php?t=56825

DF Hook Frontend plugin is included with this plugin, but still requires the Hookmod from AdminOP:
http://www.adminop.net/modules.php?name=Downloads&d_op=viewdownloaddetails&lid=73&ttitle=Hook_Mod_Windows

To be able to get a full player listing on the scoreboard, you'll need to install WebMod and set the appropriate cvars for this plugin.
WebMod site: http://djeyl.net/w.php


Credits:
Borrowed some code from Space Headed's bot_api.sma
Borrowed some code from KCE's attack blocking tutorial (http://forums.alliedmods.net/showthread.php?t=41265)
Thanks to Lola for letting me use her awesome server for testing purposes.
	http://1337bunnies.com
	66.55.131.63:27015
Thanks to r33d and Woofie for testing and suggestions.


Supported Languages:
Not Applicable... yet.


Change Log:
Key (+ added | - removed | c changed | f fixed)

v2.0a4 (SEPT ??, 2007)
+: New db admin commands: climb_dbwho, climb_dbmap, climb_dbplayer, climb_dbdelete.
+: High scores now saves boosts, cps/gcs, and weapon used.
+: New high scores sorting method.
+: Breakable objects with health less than 9999 are now removed at map load.
+: Duplicate names in database are now appended with "(#)" like the game scoreboard.
+: Client commands 'cp+' and 'cp-' to cycle through checkpoints.
+: Cvars mp_autoteambalance and mp_limitteams are set to 0 when a start button is found, and reverted when the map changes.
+: Support for a2 maps start/finish buttons.
+: Commands measure and measure2.
+: Command 'weapons', gives client one of each speed weapon.
+: Ranking by weapon speed. ex: If you beat a map with AWP, you'll be ranked above other weapon scores.
+: Command countdown to have time countdown from your best time instead of counting up.
+: Instantly switch to spectator without death animation.
-: Plugin ad on client connect.
-: WebMod support since it is a huge security vulnerability.
-: Cvars: climb_webmod, climb_stats_path, climb_stats_url, ip_internal, ip_external
c: Tweaked semiclip to hopefully eliminate lag problems reported by some people.
c: Cvar climb_water_nodraw changes take effect without reloading map.
c: Database now stores multiple records per map per player.
c: Cvar climb_db_pass is now protected.
c: Optimized html layout of scoreboards to fit more data without using WebMod.
c: Command '/stuck' is now an alias for 'cp-' and doesn't deduct from cp count.
c: Start and restart commands now tp client to the start button (Spawn and respawn still tp to spawn point).
c: Clients now spawn at start button.
c: USP and M4 forced to be always silent.
c: Guns reload with only 2 bullets, admins get unlimited clip for silenced guns.
c: Timeout on knife slash. (The sound makes my ears bleed)
c: Set 0.4 second timeout on cp, gc, and ungc commands.
c: Set 2 second timeout on usp and scout commands.
c: Re-enabled default flashlight, at least untill I recode the custom one.  Custom NVG still in place.
c: Respawn command now stops your timer, to prevent cheating in some maps.
f: Bug where maps with built in auto-heal and healing doors didn't give 511 hp. (ex: kz_man_streetclimb)
f: Showing high scores from previous map if client executes cmd a second time within the timeout period and current map has no scores in the DB.
f: An exploit that involved a paused player with their view attached to something else.
f: Bug where respawn command would spawn clients at maps origin if plugin was enabled without running init functions.
f: Bug where multiple players could connect and get logged in with the same db user id.

v2.0a3 (JUNE 21, 2007)
+: New alias for restart, /start.
+: HP set to 512 when finished as god mode replacement. God mode prevents people from seeign each others names.
+: Replicate hud messages and timer to spectators.
+: Replacement flashlight/nightvision.  Change color with cvars climb_light_r, ..._g, ..._b
+: Cvar climb_start_respawn: eliminates unnecessary respawning, admin can enable if needed
+: Cvar climb_water_nodraw: Makes func_water entities invisible to help with players FPS issues(off by default).
+: Fake client added to CT team to prevent round end.  Automatically leaves when the server is almost full, and rejoins as it empties.
+: Two new commands, /scout and /usp, to get a scout or usp if you need one, or more ammo.
+: New command /ungocheck or /ungc.  Takes you to where you were before you last used gocheck.
+: Can use chooseteam button or kill command to switch between spectating and playing.
+: Plugin automatically disables on map change.  Automatically enables if it finds a start button in new map.  Setting the cvar climb to 1 in a map config will force the plugin to run.
c: Spectators stay listed as CT on CS scoreboard so their rank will always show.
c: Command amx_goto can be used by any player once they've completed the map.
c: Players don't spawn with scout or c4 anymore.
c: Newer unsolid render is default.
c: New unsolid code provides perfect touch detection and eliminates problems that occurred in some maps.
c: Decreased the wait time on some of the command timeouts.
c: Replicated button use code from HL SDK.  Start and finish button use detection is now perfect.
c: In maps with built in healing climb auto heal is disabled(some maps like this have falling areas where you're not supposed to heal).
f: Lag caused by respawning is pretty much eliminated by new weapon cleanup code and not giving as many weapons on spawn.
f: MySQL query format problem for auto increment fields.
f: SQLite query problem. Replaced single quotes around strings with double quotes.
f: HLTV proxies caused two adjacent rows in scoreboard to be same color.
f: Spectator lag bug.
f: Bug where players had 511 HP on every map.

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 compatibility.  (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.
c: All internal cvar handling is now done with pointers. (With the acception of cvars used only during init functions.)
c: Solid boost only lasts 15 seconds at a time now since its usage is being tracked.
c: 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.
c: 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)
c: 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>

new const VERSION[ ] = "a3.7.4 Nov 24 15:41 MST"
new const TRKCVAR[ ] = "climb_version"

new const DONOTHING[ ] = "donothing"
new const NULLSTR[ ] = ""

#define IN_ATTACK_EITHER ( IN_ATTACK + IN_ATTACK2 )

#define SF_MAX 50 //Maximum number of start/finish commands in climb.ini

#define CPGC_TIMEOUT 0.4 //In seconds, decimal allowed
#define SPAWN_TIMEOUT 2 //In seconds, whole number only / used for start button also
#define WPN_TIMEOUT 2 //In seconds, whole number only
#define BOOST_TIMEOUT 5 //In seconds, whole number only
#define KNIFE_TIMEOUT 2 //In seconds, whole number only

#define NAMELEN 15

#define DBADMIN ADMIN_RCON
#define VIP	ADMIN_RESERVATION

new MAXPLAYERS

//DB data
new Handle:DB_TUPLE, DB_PREFIX[11], bool:CLIMB_SAVE, DB_SERVER_ID[16]
new db_last_del_id

//Client flags arrays
#define ORIGINS_SIZE 48
new Float:origins[33][ORIGINS_SIZE]
#define ORIG_UNGC 40
#define ORIG_PAUS 44
//#define ORIG_X 0
//#define ORIG_Y 1
//#define ORIG_Z 2
#define ORIG_GRAV 3

new time_stamps[33][5]//time_stamps[id][x]
#define TS_SPAWN 0
#define TS_BOOST 1
#define TS_WPN 2
#define TS_CPGC 3 //This one holds a flag, not a timestamp so it can be < 1 second
#define TS_KNIFE 4

#define TIMER_SIZE 15
new timer[33][TIMER_SIZE]//timer[id][x]:
#define TMR_CFLAGS 0	//Status
#define TMR_CNTTME 1	//Time / in deciseconds
#define TMR_CNTCPS 2	//CP Count
#define TMR_CNTGCS 3	//GC Count
#define TMR_CNTBST 4	//Boosts
#define TMR_BSTTME 5	//Best Time / in deciseconds
#define TMR_BSTCPS 6	//Best CP
#define TMR_BSTGCS 7	//Best GC
#define TMR_BSTBST 8	//Number of boosts used
#define TMR_SESFIN 9	//Finished this session
#define TMR_MAPFIN 10	//Total times finished this map
#define TMR_DBUSER 11	//Database player ID; 0=not registered; -1=not registered & shared steam id
#define TMR_CPPOS  12	//Current CP offset, for cycling back and forward through cps
#define TMR_CNTWPN 13 //Current weapon rank modifier
#define TMR_BSTWPN 14 //Best score weapon rank modifier

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

//Other stuff
new hooked[33], hp = 100
new sfactions[SF_MAX][50], sfcount = 0
new ts_score, ts_hscore, bool:has_hscores = false //scoreboard timeouts
new spec_ids[33][33]
new beam_sprite

new ST_BTNS[9], FN_BTNS[9], ST_BTN_CNT = 0, FN_BTN_CNT = 0
new dyn_spawn_ids[32], dyn_spawn_count, Float:spawn_tp_orig[3], Float:start_tp_orig[3]

new bool:sclip[33],Float:post_think_vel[33][3],sc_fcount

new LIMIT_TEAMS_OLD, TEAM_BALANCE_OLD

//Cvar Pointers
new p_climb, p_auto, p_boost, p_cpprice, p_startmoney
new p_msg_r, p_msg_g, p_msg_b, p_msg_x, p_msg_y
new p_sounds, p_render
new p_stats_hsurl, p_stats_msg
new p_light_r, p_light_g, p_light_b
new p_start_respawn, p_water_nodraw
new p_allow_spectators, p_teambalance, p_limitteams

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

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

//Status timer[id][TMR_CFLAGS]&=x
#define CF_NULL        0	//Passed to change_boost() to remove all boost flags
#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_STATUSFLAGS ( CF_STOP + CF_START + CF_PAUSE )
#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_LIGHT_ON    (1<<6)
//#define CF_NO_VIP    (1<<7)
#define CF_JUST_REGD	 (1<<8)
#define CF_MEASURE     (1<<9)
#define CF_MEASURE2    (1<<10)
#define CF_COUNTDOWN   (1<<11)
#define CF_SUNGLASSES  (1<<12)

#define SILENT 1

new SVC_STATUSICON, SVC_TEAMINFO, SVC_ROUNDTIME, SVC_FLASHLIGHT, SVC_SCREENFADE

new SCORES_PATH[100], HSCORES_PATH[100]

////////////////////////////////////////////////////////////////////////////////
//	Start: Init forwards
////////////////////////////////////////////////////////////////////////////////
public plugin_init( )
{	
	register_plugin( "Climb", VERSION, "Ian Cammarata" )
	register_cvar( TRKCVAR, VERSION, FCVAR_SERVER )
	set_cvar_string( TRKCVAR, VERSION )
	
	p_climb = register_cvar( "climb", "0", FCVAR_SERVER )
	p_auto = register_cvar( "climb_auto", "1" )
	p_boost = register_cvar( "climb_boost", "1" )
	p_cpprice = register_cvar( "climb_cpprice", "0" )
	p_startmoney = register_cvar( "climb_startmoney", "1337" )
	
	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", NULLSTR )
	register_cvar( "climb_db_pass", NULLSTR, FCVAR_PROTECTED )
	register_cvar( "climb_db_name", "climb" )
	register_cvar( "climb_db_prefix", "climb_" )
	register_cvar( "climb_db_serverid", NULLSTR )
	register_cvar( "climb_db_exists", "0" )
	
	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_light_r = register_cvar( "climb_light_r", "255" )
	p_light_g = register_cvar( "climb_light_g", "255" )
	p_light_b = register_cvar( "climb_light_b", "255" )
	
	p_sounds = register_cvar( "climb_sounds", "1" )
	p_render = register_cvar( "climb_unsolid_type", "0" )
	p_start_respawn = register_cvar( "climb_start_respawn", "0" )
	p_water_nodraw = register_cvar( "climb_water_nodraw", "0" )
	
	p_stats_hsurl = register_cvar( "climb_stats_hsurl", NULLSTR ) //Use %s in place of map name
	p_stats_msg = register_cvar( "climb_stats_msg", NULLSTR )
	
	p_allow_spectators = get_cvar_pointer( "allow_spectators" )
	p_teambalance = get_cvar_pointer( "mp_autoteambalance" )
	p_limitteams = get_cvar_pointer( "mp_limitteams" )
	
	MAXPLAYERS = get_maxplayers( )
	
	TEAM_BALANCE_OLD = get_pcvar_num( p_teambalance )
	LIMIT_TEAMS_OLD = get_pcvar_num( p_limitteams )	
	
	//Message Pseudo-Constants
	SVC_STATUSICON = get_user_msgid( "StatusIcon" )
	SVC_TEAMINFO = get_user_msgid( "TeamInfo" )
	SVC_ROUNDTIME = get_user_msgid( "RoundTime" )
	SVC_FLASHLIGHT = get_user_msgid( "Flashlight" )
	SVC_SCREENFADE = get_user_msgid( "ScreenFade" )

	//These commands get blocked always.
	register_clcmd( "fullupdate", "block_cmd2" )
	
	//These commands get blocked when climb is enabled.
	register_clcmd( "chooseteam", "spectate" )
	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( "say help", "help_msg" )
	register_clcmd( "say /help", "help_msg" )
	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." )
	register_clcmd( "ungc", DONOTHING, _, "- Undo last gocheck." )
	
	//Commands related to stats account system.
	//register_clcmd( "register", "reg", _, "- (Console Only) Register for stats tracking." )
	register_clcmd( "login", "db_login", _, "- (Console Only) Login to stats account." )
	
	register_concmd( "climb_dbwho", "climb_dbwho", DBADMIN, "- List DB ID of current clients." )
	register_concmd( "climb_dbmap", "climb_dbmap", DBADMIN, "<Map Name> - List high scores for current map." )
	register_concmd( "climb_dbuser", "climb_dbuser", DBADMIN, "[DB User ID] - List high scores for given DB User ID." )
	register_concmd( "climb_dbdelete", "climb_dbdelete", DBADMIN, "[DB Score ID] - Delete score for given DB Score ID." )
	
	//temp commands
	register_concmd("climb_dbrecalc","climb_dbrecalc",DBADMIN)
	
	//Admin Commands
	register_clcmd("amx_goto","goto_player")
	
	//Climb Flashlight
	//register_impulse(100,"tog_flight")
	register_clcmd("nightvision","tog_flight")
	
	//Events
	register_event("DeathMsg","DeathMsg","a")
	register_event("ResetHUD","ResetHUD","b")
	register_event("Health","Health","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
	ts_score = get_systime( )
	ts_hscore = get_systime( )
	set_task( 0.5, "hudtime", _, _, _, "b" ) //Task to update time on clients HUD; Task set for half second to minimize flickering of the timer.
	set_task( 1.0, "spec_update", _, _, _, "b" ) //Task to update spectator array
	set_task( 5.0, "run_tasks", _, _, _, "b" )
	set_task( 0.1, "timer_tick", _, _, _, "b" )
	
	register_message( get_user_msgid( "CurWeapon" ), "CurWeapon" )

	register_forward( FM_UpdateClientData, "PostUpdateClientData", 1 )
	
	//register_dictionary("climb.txt")
	
	//Make var folder if not exists
	new path[61]
	get_localinfo( "amxx_datadir", path, 60 )
	format( path, 60, "%s/var", path )
	if( !dir_exists( path ) ) mkdir( path )
	formatex( SCORES_PATH, 99, "%s/climb_scores.html", path )
	formatex( HSCORES_PATH, 99, "%s/climb_highscores.html", path )
	
	//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 pfn_keyvalue( ent )
{
	//Create more ct spawns so everyone can join team
	if( dyn_spawn_count < 1 )
	{
		new id
		for( dyn_spawn_count = 0; dyn_spawn_count < 30; dyn_spawn_count++ )
		{
			id = create_entity( "info_player_start" )
			if( id > 0 )
			{
				dyn_spawn_ids[dyn_spawn_count] = id
				entity_set_int( id, EV_INT_iuser1, 1 )
			}
		}
	}
	
	return PLUGIN_CONTINUE
}

public plugin_cfg( )
{
	new ent, ent2, tmpstr[33], Float:tmpflt
	
	//Store ent id's for start/fin buttons, some maps change target value after timer starts
	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" ) || equal( tmpstr, "gogogo" ) )
		{
			ST_BTNS[ST_BTN_CNT] = ent
			ST_BTN_CNT++
		}
		else if( equal( tmpstr, "counter_off" ) || equal( tmpstr, "clockstopbutton" )
			|| equal( tmpstr, "clockstop" ) || equal( tmpstr, "stop_counter" ) )
		{
			FN_BTNS[FN_BTN_CNT] = ent 
			FN_BTN_CNT++
		}
		
		ent = find_ent_by_class( ent, "func_button" )
	}
	
	if( ST_BTN_CNT /*|| get_pcvar_num( p_climb )*/ )
	{
		get_brush_entity_origin( ST_BTNS[0], start_tp_orig )
		
		set_pcvar_num( p_teambalance, 0 )
		set_pcvar_num( p_limitteams, 0 )
		
		set_cvar_num( "sv_restartround", 1 )//reset timer built into map
		set_cvar_num( "sv_gravity", 800 )
		set_pcvar_num( p_climb, 1 )
		
		remove_entity_name("player_weaponstrip")
		remove_entity_name("armoury_entity")
		remove_entity_name("info_player_deathmatch")
		remove_entity_name("game_player_equip")//Remove map built in spawn weapons
		
		//Remove func_breakables with < 9999 hp	
		ent = find_ent_by_class( -1, "func_breakable" )
		while( ent > 0 )
		{
			tmpflt = entity_get_float( ent, EV_FL_health )
			if( tmpflt < 9999 ) remove_entity( ent )		
			ent = find_ent_by_class( ent, "func_breakable" )
		}
		
		//Remove neg dmg func_door that aren't targeted by a button
		ent=find_ent_by_class(-1,"func_door")
		while(ent>0)
		{
			tmpflt=entity_get_float(ent,EV_FL_dmg)
			if(tmpflt<0)
			{
				hp=floatround(tmpflt)//record hp for auto heal
				entity_get_string( ent, EV_SZ_targetname, tmpstr, 32 )
				if(strlen(tmpstr))
				{
					ent2=find_ent_by_target(-1,tmpstr)
					//entity_get_string(ent2,EV_SZ_classname,tmpstr,32)
					//if(!equal("func_button",tmpstr)){
					remove_entity(ent2)
				}
				remove_entity(ent)
				//}
				//if(!strlen(tmpstr))remove_entity(ent)
				//ent=-1
			}
			ent=find_ent_by_class(ent,"func_door")
		}
		
		if( hp < 0 ) hp = 511
		//Disable climb auto heal if there is a neg dmg trigger hurt
		else
		{
			ent=find_ent_by_class(-1,"trigger_hurt")
			while(ent>0){
				tmpflt=entity_get_float(ent,EV_FL_dmg)
				if(tmpflt<0){
					hp=0
					break
				}
				ent=find_ent_by_class(ent,"trigger_hurt")
			}
		}
		
		//If Cvar set, set func_water ents to no draw, helps many peoples FPS
		if( get_pcvar_num( p_water_nodraw ) )
		{
			ent = find_ent_by_class( -1, "func_water" )
			while( ent > 0 )
			{
				set_entity_visibility( ent, 0 )
				ent=find_ent_by_class( ent, "func_water" )
			}
		}
		
		//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" ) || equal( tmpstr, "gogogo" ) )
				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, start_tp_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, 32 )
					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
		SortStrings( map_spawns, map_spawns_count )
		entity_get_vector( map_spawns[0][1], EV_VEC_origin, spawn_tp_orig )
		
		new Float:tmpvec[3], Float:tmpvec2[3]
		tmpvec = start_tp_orig
		
		start_tp_orig[2] += 24
		
		start_tp_orig[0] += 40
		trace_line( ST_BTNS[0], tmpvec, start_tp_orig, tmpvec2 )
		if( !trace_hull( start_tp_orig, HULL_HUMAN ) && tmpvec2[2] == start_tp_orig[2] )
			return PLUGIN_CONTINUE

		start_tp_orig[0] -= 80
		trace_line( ST_BTNS[0], tmpvec, start_tp_orig, tmpvec2 )
		if( !trace_hull( start_tp_orig, HULL_HUMAN ) && tmpvec2[2] == start_tp_orig[2] )
			return PLUGIN_CONTINUE

		start_tp_orig[0] += 40
		start_tp_orig[1] += 40
		trace_line( ST_BTNS[0], tmpvec, start_tp_orig, tmpvec2 )
		if( !trace_hull( start_tp_orig, HULL_HUMAN ) && tmpvec2[2] == start_tp_orig[2] )
			return PLUGIN_CONTINUE

		start_tp_orig[1] -= 80
		trace_line( ST_BTNS[0], tmpvec, start_tp_orig, tmpvec2 )
		if( !trace_hull( start_tp_orig, HULL_HUMAN ) && tmpvec2[2] == start_tp_orig[2] )
			return PLUGIN_CONTINUE

		start_tp_orig = spawn_tp_orig
	}
	else
	{//if not kz map remove dyn created spawns
		while( dyn_spawn_count >= 0 )
		{
			ent = dyn_spawn_ids[dyn_spawn_count]
			entity_get_string( ent, EV_SZ_classname, tmpstr, 32 )
			//entity_get_string(dyn_spawn_ids[i],EV_SZ_classname,tmpstr,32)
			if( equal( tmpstr, "info_player_start" ) && entity_get_int( ent, EV_INT_iuser1 ) == 1 )
				remove_entity(ent)
			dyn_spawn_count--
		}
	}
	return PLUGIN_CONTINUE
}

public plugin_precache( )
{
	beam_sprite = precache_model( "sprites/laserbeam.spr" )
	return PLUGIN_CONTINUE
}

stock Float:float_clamp( Float:curval, Float:minval, Float:maxval )
{
	if( curval < minval )return minval
	if( curval > maxval )return maxval
	return curval
}

////////////////////////////////////////////////////////////////////////////////
//	End: Init forwards
////////////////////////////////////////////////////////////////////////////////
public client_putinserver( id )
{
	if( get_pcvar_num( p_climb ) && !is_user_bot( id ) && !is_user_hltv( id ) )
	{
		timer[id][TMR_DBUSER] = 0
		
		//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
			}
		
		timer[id][TMR_CFLAGS] += CF_STOP
		
		new ida[1]
		ida[0]=id
		
		if( CLIMB_SAVE && timer[id][TMR_DBUSER] < 1 )
			set_task( 5.0, "auto_login", 0, ida, 1 )
	}
	return PLUGIN_CONTINUE
}

public client_disconnect( id )
{
	if( get_pcvar_num(p_climb) )
	{
		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] = NULLSTR		
		
		if( timer[id][TMR_CFLAGS] & CF_START ) change_status( id, CF_PAUSE ) //Pause if running
		steamid[savepos] = saveid //save steamid to position reference
		originssave[savepos] = origins[id] //save origins 
		timersave[savepos] = timer[id] //save timer array
		
		//clear data for new client in that slot
		for( new i = 0; i < 48; i++ ) origins[id][i] = 0.0
		for( new i = 0; i < 14; i++ ) timer[id][i] = 0
		hooked[id] = 0
		sortcssb( ) //Update frags to reorder scoreboard
	}
	return PLUGIN_CONTINUE
}
////////////////////////////////////////////////////////////////////////////////
//	Start: CP/GC functions
////////////////////////////////////////////////////////////////////////////////
public set_cpgc_timeout( id )
{
	//Set timeout flag and set_task to clear it
	new ida[1]
	ida[0] = id
	time_stamps[id][TS_CPGC] = 1
	set_task( CPGC_TIMEOUT, "clear_cpgc_timeout", _, ida, 1 )
}

public clear_cpgc_timeout( ida[1] )
	return time_stamps[ida[0]][TS_CPGC]=0

//Save a checkpoint
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] )//If they're not hooked...
		{
			new Float:vel[3]
			entity_get_vector( id, EV_VEC_velocity, vel )
			if( vel[2] >= 0 )//and they're not falling...
			{
				new Float:coords[3]
				entity_get_vector( id, EV_VEC_origin, coords )
				if( coords[0] || coords[1] || coords[2] )//and they're not at world origin, then save checkpoint
				{
					if( timer[id][TMR_CPPOS] > 0 ) timer[id][TMR_CPPOS]--
					new cppos = timer[id][TMR_CPPOS] * 4
					
					//If cp position is 0 then bump all cps back before saving.
					if( !cppos ) for(new i=39; i>3 ;i-- ) origins[id][i] = origins[id][i-4]
					
					for( new i=0; i<3; i++ ) origins[id][cppos + 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_CNTCPS]++
						formatex(msg,99,"Checkpoint saved. (%d CPS/ %d GCS/ %d Boosts)",timer[id][TMR_CNTCPS],timer[id][TMR_CNTGCS],timer[id][TMR_CNTBST])
					}
					clmsg(id,msg)
					
					set_cpgc_timeout( id )
				}
				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
}

//Go to most recent checkpoint
public gocheck( id )
{
	if( get_pcvar_num( p_climb ) && isalive( id ) && notpaused( id ) && !time_stamps[id][TS_CPGC] )
	{
		if( origins[id][0] || origins[id][1] || origins[id][2] )
		{
			new Float:coords[3]
			
			//Store ungocheck data
			entity_get_vector( id, EV_VEC_origin, coords )
			for( new i=0; i<3; i++ ) origins[id][ORIG_UNGC + i] = coords[i]
			origins[id][ORIG_UNGC + 3] = entity_get_float( id, EV_FL_gravity )
			
			//Do gocheck
			new cppos = timer[id][TMR_CPPOS] * 4
			for( new i=0; i<3; i++ ) coords[i] = origins[id][cppos + 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_CNTGCS]++
				formatex(msg,99,"Checkpoint restored. (%d CPS/ %d GCS/ %d Boosts)",timer[id][TMR_CNTCPS],timer[id][TMR_CNTGCS],timer[id][TMR_CNTBST])
			}
			clmsg(id,msg)
			
			set_cpgc_timeout( id )
		}
		else{
			clmsg(id,"You must make a checkpoint first.")
			return 0
		}
	}
	return 1
}

//Undo a gocheck
public ungocheck( id )
{
	if( get_pcvar_num(p_climb) && isalive(id) && notpaused(id) && !time_stamps[id][TS_CPGC] )
	{
		if( origins[id][ORIG_UNGC] || origins[id][ORIG_UNGC + 1] || origins[id][ORIG_UNGC + 2])
		{
			new Float:coords[3]
			for( new i=0; i<3; i++ )coords[i] = origins[id][ORIG_UNGC + i]
			teleport( id, coords )
			entity_set_float( id, EV_FL_gravity, origins[id][ORIG_UNGC + 3] )
			if( timer[id][TMR_CFLAGS] & CF_START ) timer[id][TMR_CNTGCS]++
			clmsg( id, "You have been ungochecked." )
			
			set_cpgc_timeout( id )
		}
		else clmsg(id,"Cannot ungocheck.")
	}
}

//Cycle back through CPs
public cp_back( id )
{
	if( get_pcvar_num( p_climb ) && isalive( id ) && notpaused( id ) && !time_stamps[id][TS_CPGC] )
	{
		if( timer[id][TMR_CPPOS] < 9 ) timer[id][TMR_CPPOS]++
		new cppos = timer[id][TMR_CPPOS] * 4
		if( origins[id][cppos] || origins[id][cppos + 1] || origins[id][cppos + 2] )
		{
			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][cppos + i]
			entity_set_float( id, EV_FL_gravity, origins[id][cppos + ORIG_GRAV] )
			teleport( id, coords )
			
			if( timer[id][TMR_CFLAGS] & CF_START ) timer[id][TMR_CNTGCS]++
			
			/*new msg[100]="Previous checkpoint restored."
			if( timer[id][TMR_CFLAGS] & CF_START )
			{
				timer[id][TMR_CNTCPS]--
				formatex( msg, 99, "Previous checkpoint restored. (%d CPS/ %d GCS/ %d Boosts)",\
					timer[id][TMR_CNTCPS], timer[id][TMR_CNTGCS], timer[id][TMR_CNTBST] )
			}
			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 )
			}*/
			
			set_cpgc_timeout( id )
		}
		else
		{
			clmsg(id,"You have no previous checkpoints remaining.")
			timer[id][TMR_CPPOS]--
		}
	}
	return PLUGIN_HANDLED
}

//Cycle forward through CPs
public cp_forward( id )
{
	if( get_pcvar_num( p_climb ) && isalive( id ) && notpaused( id ) && !time_stamps[id][TS_CPGC] && timer[id][TMR_CPPOS] > 0 )
	{
		timer[id][TMR_CPPOS]--
		new cppos = timer[id][TMR_CPPOS] * 4
		
		new Float:coords[3]
		for( new i=0; i<3; i++ ) coords[i] = origins[id][cppos + i]
		entity_set_float( id, EV_FL_gravity, origins[id][cppos + ORIG_GRAV] )
		teleport( id, coords )
		
		if( timer[id][TMR_CFLAGS] & CF_START ) timer[id][TMR_CNTGCS]++
		
		set_cpgc_timeout( id )
	}
	return PLUGIN_HANDLED
}
////////////////////////////////////////////////////////////////////////////////
//	End: CP/GC functions
////////////////////////////////////////////////////////////////////////////////
public change_status( id, newstat )
{
	new cflags_old = timer[id][TMR_CFLAGS]
	new cflags_new = cflags_old & ~CF_STATUSFLAGS

	if( newstat & CF_PAUSE )
	{
		if( cflags_old & CF_PAUSE ) //unpause
		{
			cflags_new += CF_START
			set_entity_flags( id, FL_FROZEN, 0 )
			unsolid( id )
			entity_set_float( id, EV_FL_gravity, origins[id][ORIG_PAUS + ORIG_GRAV] )
			clmsg( id, "UNPAUSED" )
			sfexec( id, 2 )
		}
		else if( cflags_old & CF_START ) //pause
		{
			cl_pause( id )
			cflags_new += CF_PAUSE
		}
		else
		{
			clmsg( id, "You must start the timer before you can pause." )
			cflags_new = cflags_old
		}
	}
	else cflags_new += newstat
	timer[id][TMR_CFLAGS] = cflags_new
	return PLUGIN_HANDLED
}

public cl_pause( id )
{
	if( is_climber_alive( id ) )
	{
		unsolid( id )
		set_rendering( id, kRenderFxGlowShell, 0, 0, 255, kRenderTransColor, 1 )
		set_entity_flags( id, FL_FROZEN, 1 )
		clmsg( id, "PAUSED - say '/unpause' to resume." )
	}
	return PLUGIN_HANDLED
}

public timer_tick( )
{
	new players[32], num, id
	get_players( players, num, "ach" )
	
	for( new i = 0; i < num; i++ )
	{
		id = players[i]
		if( timer[id][TMR_CFLAGS] & CF_START )
			timer[id][TMR_CNTTME]++
	}
}

public stop( id )
{
	if( get_pcvar_num( p_climb ) && timer[id][TMR_CFLAGS] & CF_START )
	{
		change_status( id, CF_STOP )
		heal(id)
		//Erase start/finish time stamps, prevents crazy number on scoreboard
		timer[id][TMR_CNTTME] = 0
		
		sfexec(id,4)	//Execute commands from start/finish config
	}
	return PLUGIN_HANDLED
}

//Start borrowed code : from KCE's attack blocking tutorial (http://forums.alliedmods.net/showthread.php?t=41265)
public PostUpdateClientData( id, sendweapons, cd_handle )
{
	if( get_pcvar_num( p_climb ) && is_climber_alive( id ) )
	{
		new wpn, clip, ammo
		wpn = get_user_weapon( id, clip, ammo )
		if( wpn == CSW_KNIFE )
		{
			new remain = check_timeout( 0, time_stamps[id][TS_KNIFE], KNIFE_TIMEOUT, _, 1 ) 
			if( remain )
			{
				set_cd(cd_handle, CD_flNextAttack, halflife_time() + remain )
				return FMRES_HANDLED
			}
		}
	}
	return FMRES_IGNORED
}
//End borrowed code

public CurWeapon( msg_id, msg_dest, id )
{
	new wpn = get_msg_arg_int( 2 )
	if( !get_pcvar_num( p_climb ) || !( 0 < wpn <= 30 ) )
		return PLUGIN_CONTINUE
	
	static client_oldwpn[33]	
	if( wpn != client_oldwpn[id] )
		rank_mod_update( id )
	
	client_oldwpn[id] = wpn
	
	if( wpn == CSW_C4 ||
			wpn == CSW_HEGRENADE ||
			wpn == CSW_FLASHBANG ||
			wpn == CSW_SMOKEGRENADE ||
			wpn == CSW_KNIFE
	) return PLUGIN_CONTINUE
	
	//If it's a gun, update pack and clip ammo & silencer
	new wpn_name[17]
	get_weaponname( wpn, wpn_name,16 )
	new wpn_id = find_ent_by_owner( -1, wpn_name, id )
	if( !wpn_id ) return PLUGIN_CONTINUE
	
	new clip = get_msg_arg_int( 3 )
	new maxammo = 2
	
	//Force silencer w/o animation
	if( wpn == CSW_USP || wpn == CSW_M4A1 || wpn == CSW_TMP )
	{
		if( wpn != CSW_TMP ) cs_set_weapon_silen( wpn_id, 1, 0 )
		maxammo = 10
		
		//Unlimited ammo for VIPs
		if( get_user_flags(id) & VIP )
		{
			cs_set_weapon_ammo( wpn_id, 12 )
			if( clip != 12 )return PLUGIN_HANDLED
			set_msg_arg_int( 3, ARG_BYTE, 12 )
			return PLUGIN_CONTINUE
		}
	}
	
	//Set clip and backpack ammo
	cs_set_user_bpammo( id, wpn, maxammo - clip )
	if( clip > maxammo )
	{
		set_msg_arg_int( 3, ARG_BYTE, maxammo )
		cs_set_weapon_ammo( wpn_id, maxammo )
	}
	
	return PLUGIN_CONTINUE
}

public rank_mod_update( id )
{
	new rank, maxspeed = pev( id, pev_maxspeed )
	switch( maxspeed )
	{
		case 210:
			rank = 0
		case 220:
			rank = 1
		case 230:
			rank = 2
		case 235:
			rank = 3
		case 240:
			rank = 4
		case 245:
			rank = 5
		case 250:
			rank = 6
		case 260:
			rank = 7
	}
	
	if( rank > timer[id][TMR_CNTWPN] )
			timer[id][TMR_CNTWPN] = rank
	
	new msg[100]
	format( msg, 99, "Weapon Max Speed: %d^nRank Modifier: %d", maxspeed, rank )
	clmsg( id, msg )
}

public get_weapon_name( wpn_rank, ret_name[], len )
{
	new name[21]
	switch( wpn_rank )
	{
		case 0:
			name = "AWP"
		case 1:
			name = "Para"
		case 2:
			name = "M4A1"
		case 3:
			name = "SG552"
		case 4:
			name = "Famas"
		case 5:
			name = "P90"
		case 6:
			name = "USP"
		case 7:
			name = "Scout"
	}
	formatex( ret_name, len, name )
}

public DeathMsg( )
{//Respawn client when they die unless switching to spec
	if( get_pcvar_num( p_climb ) )
	{
		new id = read_data(2)
		set_entity_flags( id, FL_FROZEN, 0 )//Unfreeze in case of switch to spectator.
		new ida[1]
		ida[0]=id
		if(task_exists(100+id))remove_task(100+id)
		if(isct(id)){
			set_task(0.2,"respawn_task",100+id,ida,1)
		}
	}
	return PLUGIN_HANDLED
}

public respawn_task(ida[]){//used by death_msg function
	new id=ida[0]
	if( !get_user_team( id ) || is_climber_alive( id ) || !isct( id ) )
		return PLUGIN_HANDLED
	//cs_user_spawn(id)
	climb_user_spawn( id, 0 )
	return PLUGIN_HANDLED
}

public frespawn( id )
{
	stop(id)
	if( is_climber_alive( id ) ) teleport( id, spawn_tp_orig )
	else if( !check_timeout( id, time_stamps[id][TS_SPAWN], SPAWN_TIMEOUT ) )
	{
		//if( get_user_team ( id ) == 3 ) cs_set_user_team( id, 2 )
		//cs_user_spawn( id )
		climb_user_spawn( id )
	}
	return PLUGIN_HANDLED
}

public tp_start_btn( id )
{
	if ( !is_climber_alive( id ) )
	{
		//if( get_user_team ( id ) == 3 ) cs_set_user_team( id, 2 )
		//cs_user_spawn( id )
		climb_user_spawn( id )
	}
	if( teleport( id, start_tp_orig ) ) return
	clmsg( id, "This map doesn't have a start button." )
}

stock climb_user_spawn( id, give_wpns = 1 )
{
	cs_user_spawn( id )
	
	set_pev( id, pev_movetype, MOVETYPE_WALK )
	set_pev( id, pev_solid, SOLID_BBOX )
	set_pev( id, pev_effects, 0 )
	set_pev( id, pev_deadflag, DEAD_NO )
	set_pev( id, pev_takedamage, DAMAGE_AIM )
	set_pev( id, pev_health, 100 )
	
	cs_set_user_team( id, 2 )
	
	if( give_wpns )
	{
		give_item( id, "weapon_knife" )
		give_item( id, "weapon_usp" )
	}
}

public ResetHUD( id )
{//Spawned
	if( get_pcvar_num( p_climb ) )
	{
		if( !is_user_bot( id ) && !is_user_hltv( id ) )
		{			
			flight_icons( id )//redraw flashlight hud icons
			set_msg_block( SVC_STATUSICON, 2 )//Block buy menu
			
			if( is_climber_alive( id ) )
			{
				//set_pev( id, pev_movetype, MOVETYPE_WALK )
				//cs_set_user_team( id, 2 )
			
				heal( id )
				sfexec( id, 1 )//Execute commands from start/finish config
				if( get_user_flags( id ) & VIP ) cs_set_user_scoreattrib( id, 4 )//Show admin as VIP on Scoreboard.
				
				if( timer[id][TMR_CFLAGS] & CF_PAUSE )
				{//If they are paused tp to pause spot and freeze them again.
					new Float:coords[3]
					for( new i=0; i<3; i++ ) coords[i] = origins[ id - 1 ][ ORIG_PAUS + i ]
					teleport( id, coords )
					cl_pause( id )
				}
				
				//Teleport to cp, start, or primary spawn position
				else if( origins[ id - 1 ][ 0 ] || origins[ id - 1][ 1 ] || origins[ id - 1 ][ 2 ]) gocheck( id )
				else if( !teleport( id, start_tp_orig ) ) teleport( id, spawn_tp_orig ) 
			}
		}
		sortcssb( )
	}
	else set_msg_block( SVC_STATUSICON, 0 )
}

public run_tasks( )
{
	updatebot( )
	check_cvars( )
}

public updatebot( )
{
	if(get_pcvar_num(p_climb))
	{
		//new players[32], cl
		//get_players( players, cl, "ach" )
		new id = find_player( "i" )
		//if(0<cl<3&&!id){
		new cl = get_playersnum( )
		if( cl < MAXPLAYERS - 2 && !id )
		{
			//Start borrowed code : from Space Headed's AMXX Bot(bot_api.sma) v0.5.1
			new name[32]
			format( name, 31, "Climb v%s", VERSION )
			id = engfunc( EngFunc_CreateFakeClient, name )
			if( pev_valid( id ) )
			{
				engfunc( EngFunc_FreeEntPrivateData, id )
				dllfunc( MetaFunc_CallGameEntity, "player", id )
				set_user_info( id, "rate", "3500" )
				set_user_info( id, "cl_updaterate", "25" )
				set_user_info( id, "cl_lw", "1" )
				set_user_info( id, "cl_lc", "1" )
				set_user_info( id, "cl_dlmax", "128" )
				set_user_info( id, "cl_righthand", "1" )
				set_user_info( id, "_vgui_menus", "0" )
				set_user_info( id, "_ah", "0" )
				set_user_info( id, "dm", "0" )
				set_user_info( id, "tracker", "0" )
				set_user_info( id, "friends", "0" )
				set_user_info( id, "*bot", "1" )
				set_pev( id, pev_flags, pev( id, pev_flags ) | FL_FAKECLIENT )
				set_pev( id, pev_colormap, id )
				
				new msg[128]
				dllfunc( DLLFunc_ClientConnect, id, name, "127.0.0.1", msg )
				dllfunc( DLLFunc_ClientPutInServer, id )
				engfunc( EngFunc_RunPlayerMove, id, Float:{0.0,0.0,0.0}, 0.0, 0.0, 0.0, 0, 0, 76 )
				//End borrowed code
				
				cs_set_user_team( id, 2 )
				cs_user_spawn( id )
			}
		}
		//else if(cl>2&&id)server_cmd("kick #%d",get_user_userid(id))
		else if( cl == MAXPLAYERS - 1 && id )
		{
			set_entity_visibility( id, 1 )
			server_cmd( "kick #%d", get_user_userid( id ) )
		}
		else if( is_user_bot( id ) && id )
		{
			set_entity_visibility( id, 0 )
			entity_set_int( id, EV_INT_solid, SOLID_NOT )
			set_pev( id, pev_takedamage, DAMAGE_NO )
			entity_set_vector( id, EV_VEC_origin, Float:{999999,999999,999999} )
			call_think( id )
		}
	}
}

public check_cvars( )
{
	static bool:nodraw
	if( get_pcvar_num( p_water_nodraw ) && !nodraw )
	{
		new ent = find_ent_by_class( -1, "func_water" )
		while( ent > 0 )
		{
			set_entity_visibility( ent, 0 )
			ent = find_ent_by_class( ent, "func_water" )
		}
		nodraw = true
	}
	else if( !get_pcvar_num( p_water_nodraw ) && nodraw )
	{
		new ent = find_ent_by_class( -1, "func_water" )
		while( ent > 0 )
		{
			set_entity_visibility( ent, 1 )
			ent = find_ent_by_class( ent, "func_water" )
		}
		nodraw = false
	}
}

//Calculate a timeout, returns time remaining on timeout
stock check_timeout( id, &checktime, freq = 1, curtime = -1, nowrite = 0 )
{
	if( curtime < 0 ) curtime = get_systime( )
	
	new dif = checktime - curtime
	if( dif > 3600 ) checktime = curtime + freq
	
	if( curtime >= checktime )
	{
		if( !nowrite ) checktime = curtime + freq
		return 0
	}
	
	new msg[76]
	if( id > 0)
	{
		format( msg, 75, "You must wait %d more seconds to use this command again.", dif )
		clmsg( id, msg )
	}
	 
	return dif
}

stock change_boost( id, newboost, silent = 0 )
{//Change Boost flags
	if( !( cvar_enabled( id, p_boost, silent ) && isalive( id ) ) && notpaused( id ) ) return PLUGIN_HANDLED
	
	new msg[151], rmflag, cflags = timer[ id - 1 ][ 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 - 1 ][ TMR_CFLAGS ] -= rmflag
	if( !( rmflag & newboost ) && \
		!check_timeout( id, time_stamps[id][TS_BOOST], BOOST_TIMEOUT, ( cflags & CF_START ? timer[id][TMR_CNTTME] / 10 : get_systime() ) ) )
	{
		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 - 1 ][ TMR_CFLAGS ] & CF_START ) timer[ id - 1 ][ TMR_CNTBST ]++
		}
		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 - 1 ][ TMR_CFLAGS ] |= newboost
	}
	if(strlen(msg))clmsg(id,msg)
	return PLUGIN_HANDLED
}

//Task to auto disable solid boost after timeout
public solid_boost_timer( ida[] )
	change_boost( ida[0], CF_SOLID )

/*public server_frame( )
{//Semi-clip
	if( get_pcvar_num( p_climb ) )
	{
		new players[32], bool:skip[32], num, i, j
		new Float:orig_i[3], Float:orig_j[3]
		new Float:vel_i[3], Float:vel_iz[3]
		new id_i, id_j, cflags_i, cflags_j
		get_players( players, num, "ac" )
		for( i = 0; i < num; i++ )
		{
			id_i = players[i]
			cflags_i = timer[id_i][TMR_CFLAGS]
			
			if( !( cflags_i & CF_PAUSE ) )
			{				
				entity_get_vector( id_i, EV_VEC_origin, orig_i )
				entity_get_vector( id_i, EV_VEC_velocity, vel_i )
				
				vel_iz[0] = vel_i[0] > 0 ? -70.0 : 70.0
				vel_iz[1] = vel_i[1] > 0 ? -70.0 : 70.0
				vel_iz[2] = vel_i[2] > 0 ? -120.0 : 120.0

				if( !vel_i[0] ) vel_i[0] = vel_iz[0] * -1
				if( !vel_i[1] ) vel_i[1] = vel_iz[1] * -1
				if( !vel_i[2] ) vel_i[2] = vel_iz[2] * -1
				
				for( j=0; j<num; j++ )
				{
					if( i != j )
					{
						id_j = players[j]
						cflags_j = timer[id_j][TMR_CFLAGS]
						
						if( ( !( cflags_i & CF_SOLID && cflags_j & CF_SOLID ) || skip[i] ) && !( cflags_j & CF_PAUSE ) )
						{
							entity_get_vector( id_j, EV_VEC_origin, orig_j )
							
							if( is_between_f( orig_j[0], orig_i[0] + vel_iz[0], orig_i[0] + vel_i[0] ) && 
									is_between_f( orig_j[1], orig_i[1] + vel_iz[1], orig_i[1] + vel_i[1] ) &&
									is_between_f( orig_j[2], orig_i[2] + vel_iz[2], orig_i[2] + vel_i[2] ) )
							{
								unsolid( id_i )
								unsolid( id_j )
								skip[i] = true
								skip[j] = true
								//maybe reset j first time i runs this, to unsolid anyone they might be boosting with
							}
						}
					}
				}
				if( !skip[i] ) solid( id_i )
			}
		}
	}
	return PLUGIN_CONTINUE
}*/
public server_frame( )
{
	if( !get_pcvar_num(p_climb) )
		return PLUGIN_CONTINUE
	
	sc_fcount++
	if( sc_fcount > 9 ) sc_fcount = 0
	
	//Semi-clip
	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")
	//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
}

stock bool:is_between_f( Float:mid, Float:a, Float:b )
{
	if( a < b && ( a < mid < b ) ) return true 
	if( a > b && ( a > mid > b ) ) return true
	return false
}

stock Float:vector_z_distance_f( Float:vec1[3], Float:vec2[3] )
{
	return Float:floatsqroot( ( vec1[2] - vec2[2] ) ^ 2.0 )
}

stock Float:vector_xy_distance_f( Float:vec1[3], Float:vec2[3] )
{
	return Float:floatsqroot( ( ( vec1[0] - vec2[0] ) ^ 2.0 ) + ( ( vec1[1] - vec2[1] ) ^ 2.0 ) )
}

public client_PreThink( id )
{
	if( !get_pcvar_num( p_climb ) || is_user_bot( id ) || !is_climber_alive( id ) )
		return PLUGIN_CONTINUE
	
	if( sclip[id] && sc_fcount == 9 ) entity_set_int( id, EV_INT_solid, SOLID_BBOX )
	
	//Detect button use. Replicated from HL SDK
	if( get_user_button( id ) & IN_USE && !( get_user_oldbutton( id ) & IN_USE ) )
	{
		new entlist[3], count
		count = find_sphere_class( id, "func_button", 64.0, entlist, 3 )
		if( count )
		{
			new Float:orig[3]
			for( new i = 0; i < count; i++ )
			{
				get_brush_entity_origin( entlist[i], orig )
				if( is_in_viewcone( id, orig ) ) button_used( id, entlist[i] )
      }
		}
	}
	
	new cflags = timer[id][TMR_CFLAGS]
	
	//Double and super jump boosts
	if( get_pcvar_num( p_boost ) )
	{
		new 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, 400, 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] )
					float_clamp( post_think_vel[id][0], -150.0, 150.0 )
					float_clamp( post_think_vel[id][1], -150.0, 150.0 )
					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 }
				}
			}
		}
	}
	
	//Prevent drowning
	// waterlevel 0 - not in water
	// waterlevel 1 - feet in water
	// waterlevel 2 - waist in water
	// waterlevel 3 - head in water
	if( pev( id, pev_waterlevel ) == 3 ) set_pev( id, pev_waterlevel, 2 )
	
	new button = pev( id, pev_button )
	new oldbutton = pev( id, pev_oldbuttons )
	
	//If measure flag is set, call measure function
	if( !( oldbutton & IN_ATTACK ) && button & IN_ATTACK )
		if( cflags & CF_MEASURE ) do_measure( id )
		else if( cflags & CF_MEASURE2 ) do_measure2( id, 1 )
	
	//Knife attack timeout
	new wpn, clip, ammo
	wpn = get_user_weapon( id, clip, ammo )
	if( wpn == CSW_KNIFE )
	{
		 
		if( button & IN_ATTACK || button & IN_ATTACK2 )
		if( check_timeout( 0, time_stamps[id][TS_KNIFE], KNIFE_TIMEOUT ) )
			set_pev( id, pev_button, pev( id, pev_button ) & ~IN_ATTACK_EITHER )
	}
	return PLUGIN_CONTINUE
}

public client_PostThink(id)
{	
	if( !( get_pcvar_num(p_climb) && get_pcvar_num(p_boost) && is_climber_alive( id ) ) || is_user_bot(id) )
		return PLUGIN_CONTINUE
		
	if( sclip[id] ) entity_set_int( id, EV_INT_solid, SOLID_NOT )
	
	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_CNTBST]++	//Increment client boost count
			change_boost( id, CF_NULL )
		}
		else if( !is_finished(id) )change_boost( id, CF_NULL )
	}
	
	//Store pause data
	if( !( timer[id][TMR_CFLAGS] & CF_PAUSE ) )
	{
		new Float:coords[3]
		entity_get_vector( id, EV_VEC_origin, coords )
		for( new i=0; i<3; i++ ) origins[id][ORIG_PAUS+i] = coords[i]
		origins[id][ORIG_PAUS+3] = entity_get_float( id, EV_FL_gravity )
	}
	return PLUGIN_CONTINUE
}

public button_used( id, button )
{
	new btn_type = 0
	for( new i = 0; i < ST_BTN_CNT || i < FN_BTN_CNT; i++ )
	{
		if( i < ST_BTN_CNT )
			if( button == ST_BTNS[i] )
			{
				btn_type = 1
				break
			}
		if( i < FN_BTN_CNT )
			if( button == FN_BTNS[i] )
			{
				btn_type = 2
				break
			}
	}
	
	//new cflags = timer[id][TMR_CFLAGS]
	
	if( btn_type == 1 )//Start Button
	{
		new Float:orig[3], bool:need_respawn = true //Float:view[3]
		if( timer[id][TMR_CFLAGS] & CF_START ) need_respawn = false
		
		for( new i = 0; i < 48; i++ ) origins[id][i] = 0.0 //Erase checkpoints
		
		//Set all associated variables
		timer[id][TMR_CNTTME] = 0
		timer[id][TMR_CNTCPS] = 0
		timer[id][TMR_CNTGCS] = 0
		timer[id][TMR_CNTBST] = 0
		timer[id][TMR_CPPOS] = 0
		timer[id][TMR_CNTWPN] = 0
		time_stamps[id][TS_BOOST] = BOOST_TIMEOUT
		change_status(id,CF_START)
		
		if( timer[id][TMR_CFLAGS] & CF_COUNTDOWN ) set_countdown_time( id )
		
		//Record starting weapon
		rank_mod_update( id )
		
		//Respawn client to clear items.
		if( need_respawn && get_pcvar_num( p_start_respawn ) )
		{
			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 )
			//time_stamps[id][TS_SPAWN]=get_systime()
		}
		heal( 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" )
		}
		
		change_boost( id, CF_NULL, SILENT ) //Disable Boosts
		//time_stamps[id][TS_BOOST]=0
		clmsg(id,"Timer started. Go Go Go!!!")
		//client_print(id,print_chat,"Timer started. Go Go Go!!!")
		
		sfexec( id, 2 ) //Execute commands from start/finish config
		
		sortcssb( ) //Update frags to reorder scoreboard
	}
	else if( btn_type == 2 )//Finish Button
	{
		if( timer[id][TMR_CFLAGS] & CF_START )
		{
			new cnttme = timer[id][TMR_CNTTME] / 10
			new cntbst = timer[id][TMR_CNTBST]
			new cntcps = timer[id][TMR_CNTCPS]
			new cntgcs = timer[id][TMR_CNTGCS]
			new cntwpn = timer[id][TMR_CNTWPN]
			
			//Set client variables
			change_status( id, CF_STOP )
			timer[id][TMR_SESFIN]++
			timer[id][TMR_MAPFIN]++
			
			//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[23]
			formatex( msg, 22, "%s^t%s", getuserstatus( id ), parsetime( timer[id][TMR_CNTTME] ) )
			//clmsg(id,msg)
			//client_print(id,print_chat,msg)
			
			new wpn[11]
			get_weapon_name( cntwpn, wpn, 10 )
			
			//Announce to all
			get_user_name( id, name, 32 )
			client_print( 0, print_chat,
				"%s^t%s^t(%s/ %d CPS/ %d GCS/ %d Boosts)^tCompleted (%d, %d)",
				name, msg, wpn, cntcps, cntgcs, cntbst,
				timer[id][TMR_SESFIN], timer[id][TMR_MAPFIN]
			)
			
			new cntscore = ( ( ( cntbst > 0 ? 1 : 0 ) * 1000000 ) +
				( ( cntcps > 0 ? 1 : 0 ) * 100000 ) +
				( cntwpn * 10000 ) +
				cnttme )
			
			new bstscore = ( ( ( timer[id][TMR_BSTBST] > 0 ? 1 : 0 ) * 1000000 ) +
				( ( timer[id][TMR_BSTCPS] > 0 ? 1 : 0 ) * 100000 ) +
				( timer[id][TMR_BSTWPN] * 10000 ) +
				timer[id][TMR_BSTTME] / 10 )
				
			if( cntscore < bstscore || bstscore == 0 )
			{
				timer[id][TMR_BSTTME] = cnttme
				timer[id][TMR_BSTCPS] = cntcps
				timer[id][TMR_BSTGCS] = cntgcs
				timer[id][TMR_BSTBST] = cntbst
				timer[id][TMR_BSTWPN] = cntwpn
			}
		
			sfexec( id, 2 )//Execute commands from start/finish config
			
			if( CLIMB_SAVE ) db_save( id )
			
			sortcssb( ) //Update frags to reorder scoreboard
		}
		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)
		}
		heal(id)
	}
	return PLUGIN_HANDLED
}

//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)&VIP||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 PLUGIN_HANDLED
}

//Update frags to reorder scoreboard
public sortcssb( )
{
	new players[32], num, id
	get_players_ordered( players, num )
	for( new i = 0 ; i < num; i++ )
	{
		id = players[i]
		set_user_frags( id, 0 )
		cs_set_user_deaths( id, i + 1 )
		
		message_begin( MSG_ALL, SVC_TEAMINFO )
		write_byte( id )
		write_string( "CT" )
		message_end( )
	}
	
	//Show bot as spectator on scoreboard
	message_begin( MSG_ALL, SVC_TEAMINFO )
	write_byte( find_player( "i" ) )
	write_string( "SPECTATOR" )
	message_end( )
}

//Returns array of client id's sorted by climb time
public get_players_ordered( players[32], &num )
{
	get_players( players, num, "ch" )
	
	new i, id, pdata[32][3] //{ status, score, id }
	for( i = 0; i < num; i++ )
	{//Fill player data sorting array
		id = players[i]
		pdata[i][2] = id
		
		if( timer[id][TMR_BSTTME] )
		{//Has high score
			pdata[i][0] = 0
			pdata[i][1] = ( ( ( timer[id][TMR_BSTBST] > 0 ? 1 : 0 ) * 1000000 ) +
				( ( timer[id][TMR_BSTCPS] > 0 ? 1 : 0 ) * 100000 ) +
				( timer[id][TMR_BSTWPN] * 10000 ) +
				timer[id][TMR_BSTTME] / 10 )
		}
		else if( timer[id][TMR_CFLAGS] & CF_START || timer[id][TMR_CFLAGS] & CF_PAUSE )
		{//Has time but no high score
			pdata[i][0] = 1
			pdata[i][1] = ( ( ( timer[id][TMR_CNTBST] > 0 ? 1 : 0 ) * 1000000 ) +
				( ( timer[id][TMR_CNTCPS] > 0 ? 1 : 0 ) * 100000 ) +
				( timer[id][TMR_CNTWPN] * 10000 ) +
				timer[id][TMR_CNTTME] / 10 ) 
		}
		else pdata[i][0] = 2 //No time or high score
	}
	
	SortStrings( pdata, num )
	for( i = 0; i < num; i++ )
		players[i] = pdata[i][2]
}

/*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 false
	return true
}*/

/*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)
	//}
	if( strlen( url ) ) format( url, 99, "%sclimb_scores.html", url )
	return url
}*/

stock sb_add_tabs( str[], size)
{
	new len = strlen( str )// > 7 ? 1 : 2
	add( str[len], size, "^t")
	if( len < 8 ) add( str[len+1], size, "^t")
}

public climbscores( id )
{//Show scoreboard
	if( !get_pcvar_num( p_climb ) ) return PLUGIN_HANDLED
	
	if( get_systime() - ts_score > 2 )
	{
		new fh = fopen( SCORES_PATH, "w" )
		
		//Header-8 cols: #, name, status, time, sct/cp/gc/boost, Best, cp/gc/boost, Completed
		fprintf( fh, "<link rel=stylesheet href=http://ian.cammarata.us/sb><table><tr><td id=a>" )
		fprintf( fh, "<pre>#	Name		Status		Time	Wpn/CP/GC/Bst	Best	...		Fin</pre>" )
		fprintf( fh, "</td></tr><tr id=b><td></td></tr><tr><td><pre>" )
		
		new line[251], name[NAMELEN+2], players[32], num, tid, written_len
		new ctime, cwpn, ccp, cgc, cbst
		new btime, bwpn, bcp, bgc, bbst
		new ctime_str[20], btime_str[20]
		
		get_players_ordered( players, num )
		
		for( new i = 0; i < num; i++ )
		{
			tid = players[i]
			
			get_user_name( tid, name, NAMELEN)
			sb_add_tabs( name, NAMELEN+2 )
			
			ctime = timer[tid][TMR_CNTTME]
			cwpn = timer[tid][TMR_CNTWPN]
			ccp = timer[tid][TMR_CNTCPS]
			cgc = timer[tid][TMR_CNTGCS]
			cbst = timer[tid][TMR_CNTBST]
			format( ctime_str, 19, "%d/%d/%d/%d", cwpn, ccp, cgc, cbst )
			sb_add_tabs( ctime_str, 19 )
			
			btime = timer[tid][TMR_BSTTME]
			bwpn = timer[tid][TMR_BSTWPN]
			bcp = timer[tid][TMR_BSTCPS]
			bgc = timer[tid][TMR_BSTGCS]
			bbst = timer[tid][TMR_BSTBST]
			format( btime_str, 19, "%d/%d/%d/%d", bwpn, bcp, bgc, bbst )
			sb_add_tabs( btime_str, 19 )
			
			formatex( line, 250,
				"%s%d	%s%s	%s	%s%s	%s%d%s",
				i % 2 ? NULLSTR : "<div>",
				i + 1, htmlspecialchars( name ),
				getuserstatus( tid ),
				parsetime( ctime ), ctime_str,
				parsetime( btime ), btime_str,
				timer[tid][TMR_MAPFIN],
				i % 2 ? NULLSTR : "</div>" )
			written_len += strlen( line )
			//if( limit && written_len > ( 1263 - strlen( cust_msg ) ) ) break
			if( written_len > 1263 ) break
			fprintf( fh, line )
		}
		//1530-150-117=1263
		new cust_msg[33]
		get_pcvar_string( p_stats_msg, cust_msg, 32 )
		if( !strlen( cust_msg ) ) formatex( cust_msg, 32, "Climb %s", VERSION )
		fprintf( fh, "</pre></td></tr><tr id=d><td></td></tr><tr><td id=e></td></tr><tr>" )
		fprintf( fh, "<td id=a>%s</td></tr></table>", cust_msg )
		fclose( fh )	
		ts_score = get_systime()
	}
	
	show_motd( id, SCORES_PATH, "Current Scores" )

	return PLUGIN_HANDLED
}

//Convert seconds to time string with zero padded seconds field
public parsetime( decisec )
{
	new timestr[11], mins, sec, dec, decstr[3]
	
	sec = decisec / 10
	dec = decisec % 10 
	mins = sec / 60
	sec = sec % 60
	if( dec ) formatex( decstr, 2, ".%d", dec ) 
	formatex( timestr, 10, "%d:%s%d%s", mins, sec < 10 ? "0" : NULLSTR , sec, decstr )
	return timestr
}

public hudtime( )
{//Set clock on HUD to current climb time
	if( !get_pcvar_num( p_climb ) ) return
	
	new id, players[32], num, cltime, cflags
	get_players( players, num, "ach" )
	for( new i = 0; i < num; i++ )
	{
		id = players[i]
		cflags = timer[id][TMR_CFLAGS]
		cltime = timer[id][TMR_CNTTME] / 10 + 1
		if( cflags & CF_PAUSE )
		{
			clmsg( id, "PAUSED - say '/unpause' to resume." )
			cl_pause( id )
		}
		
		if( !( cflags & CF_COUNTDOWN ) || ( timer[id][TMR_BSTTME] >= timer[id][TMR_CNTTME] ) )
			hudtime_msg( id, cltime )
		else if( cflags & CF_PAUSE )
			hudtime_msg( id, timer[id][TMR_BSTTME] / 10 - cltime + 1 )
			
		for( new j = 1; j <= spec_ids[id][0]; j++ )
			hudtime_msg( spec_ids[id][j], cltime )
	}
}

public toggle_countdown( id )
{
	//Toggle countdown flag and print message
	timer[id][TMR_CFLAGS] ^= CF_COUNTDOWN
	if( timer[id][TMR_CFLAGS] & CF_COUNTDOWN )
	{
		if( set_countdown_time( id ) )
			clmsg( id, "Countdown enabled." )
	}
	else
		clmsg( id, "Countdown disabled." )
}

public bool:set_countdown_time( id )
{
	new bsttme = timer[id][TMR_BSTTME] / 10
	if( bsttme > 0 )
	{
		new cflags = timer[id][TMR_CFLAGS]
		if( cflags & CF_START || cflags & CF_PAUSE )
			hudtime_msg( id, bsttme - timer[id][TMR_CNTTME] / 10 )
		return true
	}
	clmsg( id, "Countdown disabled: No high score available." )
	timer[id][TMR_CFLAGS] ^= CF_COUNTDOWN
	return false
}

public hudtime_msg( id, cltime )
{
	message_begin( MSG_ONE, SVC_ROUNDTIME, _, id )
	write_short( cltime )
	message_end( )
}

public getuserstatus( id )
{//Return string describing user status
	new msg[12], cflags = timer[id][TMR_CFLAGS]
	msg = "PAUSED"
	if( cflags & CF_STOP && timer[id][TMR_SESFIN] > 0 )
		msg = "Finished"
	else if( cflags & CF_STOP )
		msg = "Not Started"
	else if( cflags & CF_START )
		msg = "Climbing"
	//else if( cflags & CF_PAUSE ) msg = "PAUSED"
	//return "PAUSED"
	return msg
}

stock clmsg( id, msg[] )
{//Show Hud Message
	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 )
	
	//Show to spectators
	for( new i = 1; i <= spec_ids[id][0]; i++ )
		show_hudmessage( spec_ids[id][i], msg )
		
	return PLUGIN_HANDLED
}
//Fill spectator data array used for msg replication
public spec_update(){
	if(get_pcvar_num(p_climb)){
		new players[32],num,id,id2,i
		for(i=1;i<33;i++)spec_ids[i][0]=0
		get_players(players,num,"bch")
		for(i=0;i<num;i++){
			id=players[i]
			id2=pev(id,pev_iuser2)
			if(id2){
				spec_ids[id2][0]++
				spec_ids[id2][spec_ids[id2][0]]=id
			}
		}
	}
	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)
	sclip[id]=false
	return PLUGIN_CONTINUE
}
//Make client unsolid
public unsolid(id)
{
	if(get_pcvar_num(p_render))set_user_rendering(id,kRenderFxHologram,0,0,0,kRenderTransAdd,0)
	else set_user_rendering(id,kRenderFxPulseSlow,0,0,0,kRenderTransTexture,50)
	entity_set_int(id,EV_INT_solid,SOLID_NOT)
	sclip[id]=true
	return PLUGIN_CONTINUE
}
////////////////////////////////////////////////////////////////////////////////
//	Start: Functions for use in if statements
////////////////////////////////////////////////////////////////////////////////
public is_climber_alive( id )
{
	new movetype = pev( id, pev_movetype )
	if( movetype ==  MOVETYPE_WALK || movetype == MOVETYPE_FLY )
		return 1
	return 0
}

public playersolid( id )
{
	if(entity_get_int(id,EV_INT_solid)==SOLID_BBOX)return 1
	return 0
}

public isct(id){//Used in IF statements a lot
	if(get_user_team(id)==2)return 1
	return 0
}

public is_finished( id )
{
	new cflags = timer[id][TMR_CFLAGS]
	if( cflags & CF_STOP && ( get_user_flags( id ) & VIP || timer[id][TMR_SESFIN] > 0 ) )
		return 1
	return 0
}

public isalive(id){//Use in IF statements to automatically print error if false
	if( is_climber_alive( id ) )return 1
	clmsg(id,"You must be alive to execute this command.")
	return 0
}

public notpaused(id){//Use in IF statements to automatically print error if false
	if(!(timer[id][TMR_CFLAGS]&CF_PAUSE))return 1
	clmsg(id,"You can't execute this command while paused.")
	return 0
}

/*public regwarn(id){
	if(timer[id][TMR_DBUSER]<1){
		saytext(id,id,"^x04You must register/login for your stats to save. Say /climbhelp for more info.")
		return 1
	}
	return 0
}*/
////////////////////////////////////////////////////////////////////////////////
//	End: Functions for use in if statements
////////////////////////////////////////////////////////////////////////////////
//	Start: Auto Heal functions
////////////////////////////////////////////////////////////////////////////////
public Health( id )
{//Called when client takes damage
	new ida[1]
	ida[0] = id
	set_task( 0.1, "damage_handle", _, ida, 1 )
}

public damage_handle( ida[1] )
{//Damage hander, delayed to provide HUD consistency
	new id = ida[0]
	if( get_pcvar_num( p_climb )&& is_climber_alive( id ) )
	{
		new chp = get_user_health( id )
		if( chp != hp )
		{
			heal( id )
			/*new wpn, atk = get_user_attacker( id, wpn )
			
			//Heal hp immediately
			if( ( atk == 0 || ( id == atk && wpn == 0) ) && !task_exists( 50 + id ) )
				heal(id)
				
			//Heal delayed
			else
			{
				if( task_exists( 50 + id ) ) remove_task( 50 + id )
				set_task( 5.0, "task_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
}

public task_heal(ida[]){//Heal client to predetermined HP required by map
	heal(ida[0])
	return PLUGIN_HANDLED
}

public heal(id){
	if( is_climber_alive( id ) )
	{
		if(is_finished(id))set_user_health(id,511)
		else if(hp)set_user_health(id,hp)
		else if(get_user_health(id)>100)set_user_health(id,100)
	}
}
////////////////////////////////////////////////////////////////////////////////
//	End: Auto Heal functions
////////////////////////////////////////////////////////////////////////////////

public pfn_touch( ent, id )
{
	if( !get_pcvar_num( p_climb ) )
		return PLUGIN_CONTINUE 
	
	//Clean up dropped stuff
	new str[21]
	entity_get_string( ent, EV_SZ_classname, str, 20 )
	if( equal( str, "weaponbox" ) || equal( str, "item_thighpack" ) )
	{
		remove_entity( ent )
		return PLUGIN_HANDLED
	}
	if( pev_valid( id ) )
	{
		entity_get_string( id, EV_SZ_classname, str, 20 )
		if( equal( str, "weaponbox" ) || equal( str, "item_thighpack" ) )
		{
			remove_entity( id )
			return PLUGIN_HANDLED
		}
	}
	else return PLUGIN_HANDLED
	
	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
}

////////////////////////////////////////////////////////////////////////////////
//	Start: Flashlight functions
////////////////////////////////////////////////////////////////////////////////
public flight_icons( id )
{
	if( timer[id][TMR_CFLAGS] & CF_LIGHT_ON )
	{
		if( !task_exists( TSK_FLIGHT + id ) )
			set_task( 0.1, "flight_msg", TSK_FLIGHT + id, _, _, "b" )
		
		message_begin( MSG_ONE, SVC_FLASHLIGHT, _, id )
		write_byte( 1 )
		write_byte( 100 )
		message_end( )
		
		message_begin( MSG_ONE, SVC_STATUSICON, _, id )
		write_byte( 1 ) //on
		write_string( "flash_beam" )
		write_byte( get_pcvar_num( p_light_r ) ) //r
		write_byte( get_pcvar_num( p_light_g ) ) //g
		write_byte( get_pcvar_num( p_light_b ) ) //b
		message_end( )
	}
	else
	{
		message_begin( MSG_ONE, SVC_FLASHLIGHT, _, id )
		write_byte( 0 )
		write_byte( 100 )
		message_end( )
		
		message_begin( MSG_ONE, SVC_STATUSICON, _, id )
		write_byte( 0 ) //off
		write_string( "flash_beam" )
		message_end( )
	}
}
public tog_flight(id){
	if(!get_pcvar_num(p_climb))return PLUGIN_CONTINUE
	if(!(timer[id][TMR_CFLAGS]&CF_LIGHT_ON)){
		timer[id][TMR_CFLAGS]+=CF_LIGHT_ON
		set_task(0.1,"flight_msg",TSK_FLIGHT+id,_,_,"b")
	}
	else timer[id][TMR_CFLAGS]-=CF_LIGHT_ON
	flight_icons(id)
	return PLUGIN_HANDLED
}

public flight_msg(tskid){
	if(get_pcvar_num(p_climb)){
		new orig[3],id=tskid-TSK_FLIGHT
		if(!(timer[id][TMR_CFLAGS]&CF_LIGHT_ON))remove_task(tskid)
		get_user_origin(id,orig)
		message_begin(MSG_ONE_UNRELIABLE,SVC_TEMPENTITY,_,id)
		write_byte(TE_DLIGHT)
		write_coord(orig[0])//x
		write_coord(orig[1])//y
		write_coord(orig[2])//z
		write_byte(60)//radius
		write_byte(get_pcvar_num(p_light_r))//r
		write_byte(get_pcvar_num(p_light_g))//g
		write_byte(get_pcvar_num(p_light_b))//b
		write_byte(4)//life
		write_byte(2)//decay
		message_end()
	}
	return PLUGIN_HANDLED
}
////////////////////////////////////////////////////////////////////////////////
//	End: Flashlight functions
////////////////////////////////////////////////////////////////////////////////
public teleport( id, Float:orig[3] )
{//Used to teleport clients, prevents client collisions and getting stuck
	//Don't tp if vector is all zeros
	if( !( orig[0] || orig[1] || orig[2] ) ) return 0
	
	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 ) < 90 ) 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
}

public delay_duck(id){//Used by teleport and timer start respawn, prevents getting stuck
	new ida[1]
	ida[0]=id
	set_task(0.01,"force_duck",_,ida,1)
	set_entity_flags(ida[0],FL_DUCKING,1)
}

public force_duck(ida[1]){//Task for delay_duck
	set_entity_flags(ida[0],FL_DUCKING,1)
}

//Used in IF statements to automatically print error if false
stock cvar_enabled( id, p_cvar, silent = 0 )
{
	if( get_pcvar_num(p_cvar) == 0 )
	{
		if( !silent )
		{
			clmsg( id, "This command is disabled." )
			client_print(id,print_chat,"This command has been disabled by the server administrator.")
		}
		return 0
	}
	return 1
}
////////////////////////////////////////////////////////////////////////////////
//	Start: Climb command handling functions
////////////////////////////////////////////////////////////////////////////////

public sunglasses( id )
{	
	//Toggle flag for this measure and print message
	timer[id][TMR_CFLAGS] ^= CF_SUNGLASSES
	if( timer[id][TMR_CFLAGS] & CF_SUNGLASSES )
	{
		message_begin( MSG_ONE, SVC_SCREENFADE, _, id )
		write_short( 1 ) //total duration
		write_short( 0 ) //time it stays one color
		write_short( 5 ) //fade type
		write_byte( 0 ) //r
		write_byte( 0 ) //g
		write_byte( 0 ) //b
		write_byte( 127 ) //a
		message_end( )
	}
	else
	{
		message_begin( MSG_ONE, SVC_SCREENFADE, _, id )
		write_short( 1 ) //total duration
		write_short( 0 ) //time it stays one color
		write_short( 0 ) //fade type
		write_byte( 0 ) //r
		write_byte( 0 ) //g
		write_byte( 0 ) //b
		write_byte( 127 ) //a
		message_end( )
	}
}

public give_scout( id )
	if( !check_timeout( id, time_stamps[id][TS_WPN], WPN_TIMEOUT ) )
		give_item( id, "weapon_scout" )

public give_usp( id )
	if( !check_timeout( id, time_stamps[id][TS_WPN], WPN_TIMEOUT ) )
		give_item( id, "weapon_usp" )

public give_weapons( id )
	if( !check_timeout( id, time_stamps[id][TS_WPN], WPN_TIMEOUT ) )
	{		
		give_item( id, "weapon_awp" )
		give_item( id, "weapon_m249" )
		give_item( id, "weapon_m4a1" )
		give_item( id, "weapon_sg552" )
		give_item( id, "weapon_famas" )
		give_item( id, "weapon_p90" )
		give_item( id, "weapon_usp" )
		give_item( id, "weapon_scout" )
	}
		

public help_msg(id)
{
	client_print( id, print_chat, "Say /climbhelp to get help with the Climb kz plugin." )
	return PLUGIN_CONTINUE
}

public climb_help(id)
	if( get_pcvar_num( p_climb ) )
		show_motd( id, "http://ian.cammarata.us/projects/climb/help/2a3/en/commands?agent=hl", "Climb Plugin Help" )

public boost_help(id)
	if( get_pcvar_num( p_climb ) )
		show_motd( id, "http://ian.cammarata.us/projects/climb/help/2a3/en/boosts?agent=hl", "Climb Plugin Help" )

public goto_player( id )
{//Admin command, teleport to client
	if( is_finished( id ) && 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
}

public spectate( id )
{//Client switch to spectator
	if( get_pcvar_num( p_climb ) && !is_user_bot( id ) && !is_user_hltv( id ) )
	{
		if( !is_climber_alive( id ) )
		{
			/*
			cs_set_user_team(id,3)//fixes respawn bug?			
			frespawn( id )
			*/
			
			climb_user_spawn( id )
			
			if( timer[id][TMR_CFLAGS] & CF_PAUSE )
			{//If they are paused tp to pause spot and freeze them again.
				new Float:coords[3]
				for( new i=0; i<3; i++ ) coords[i] = origins[ id - 1 ][ ORIG_PAUS + i ]
				teleport( id, coords )
				cl_pause( id )
			}
			
		}
		else if( get_user_flags( id ) & VIP ? 1 : ( cvar_enabled( id, p_allow_spectators ) ) )
		{			
			//Move client way outside the map so we don't have to see their retarded model
			new Float:orig[3]
			entity_get_vector( id, EV_VEC_origin, orig )
			entity_set_vector( id, EV_VEC_origin, Float:{ 99999.9, 99999.9, 99999.9 } )
			
			//Set a task to move them back to where they were originally
			new ida[4]
			ida[0] = id
			ida[1] = _:orig[0]
			ida[2] = _:orig[1]
			ida[3] = _:orig[2]
			set_task( 0.1, "tsk_spec_tp_back", _, ida, 4 )
			
			set_pev( id, pev_movetype, MOVETYPE_NOCLIP )
			set_pev( id, pev_solid, SOLID_NOT )
			set_pev( id, pev_effects, EF_NODRAW )
			set_pev( id, pev_deadflag, DEAD_DEAD )
			set_pev( id, pev_takedamage, DAMAGE_NO )
			set_entity_flags( id, FL_FROZEN, 0 )
			
			if( timer[id][TMR_CFLAGS] & CF_START ) change_status( id, CF_PAUSE )
		}
		return PLUGIN_HANDLED
	}
	return PLUGIN_CONTINUE
}

public tsk_spec_tp_back( ida[4] )
{
	new Float:orig[3]
	orig[0] = Float:ida[1]
	orig[1] = Float:ida[2]
	orig[2] = Float:ida[3]
	entity_set_vector( ida[0], EV_VEC_origin, orig )
}

public client_command( id )
{//Forward to catch all formats for commands
	if(!get_pcvar_num(p_climb))
		return PLUGIN_CONTINUE
	new cmd[21]
	read_argv(0,cmd,20)
	
	//If say command, trim "say", 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)
	
	//Ungocheck
	else if( equali(cmd,"ungc") || equali(cmd,"ungocheck") )
		ungocheck(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)
	
	//Cycle CPs reverse
	else if( equali(cmd,"cp-") || equali(cmd,"stuck") || equali(cmd,"unstuck") )
	{
		cp_back( id )
		return PLUGIN_CONTINUE
	}
	
	//Cycle CPs forward
	else if( equali(cmd,"cp+") )
	{
		cp_forward( id )
		return PLUGIN_CONTINUE
	}
	
	//Pause
	else if( equali(cmd,"pause") || equali(cmd,"unpause") )
		change_status(id,CF_PAUSE)
	
	//Stop
	else if( equali(cmd,"stop") )
		stop(id)
	
	//Time/Scores
	else if( equali(cmd,"scoreboard") || equali(cmd,"score") || equali(cmd,"scores") )
		climbscores(id)
	
	//High Scores
	else if( equali(cmd,"top10") || equali(cmd,"top15") || equali(cmd,"top") || equali(cmd,"highscores") ||\
		equali(cmd,"best") || equali(cmd,"rank") || equali(cmd,"pro15") || equali(cmd,"nub15") )
		highscores(id)
	
	//Respawn
	else if( equali( cmd, "respawn" ) || equali( cmd, "spawn" ) )
		frespawn( id )
	
	//TP to start button
	else if( equali( cmd, "restart" ) || equali( cmd, "start" ) || equali( cmd, "reset" ) )
		tp_start_btn( id )
	
	//Spectate
	else if( equali(cmd,"spectate") || equali(cmd,"spec") )
		spectate(id)
	
	//Get scout
	else if( equali( cmd, "scout" ) )
		give_scout( id )
	
	//Get usp
	else if( equali( cmd, "usp" ) )
		give_usp( id )
	
	else if( equali( cmd, "weapons" ) )
	{
		give_weapons( id )
		return PLUGIN_CONTINUE
	}
	
	else if( equali( cmd, "measure" ) )
	{
		toggle_measure( id )
		return PLUGIN_CONTINUE
	}
	
	else if( equali( cmd, "measure2" ) )
	{
		toggle_measure2( id )
		return PLUGIN_CONTINUE
	}
	
	else if( equali( cmd, "countdown" ) || equali( cmd, "countup" ) )
	{
		toggle_countdown( id )
		return PLUGIN_CONTINUE
	}
	
	else if( equali( cmd, "sunglasses" ) )
	{
		sunglasses( id )
		return PLUGIN_CONTINUE
	}
	
	//Climb Help
	else if( 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
}

////////////////////////////////////////////////////////////////////////////////
//	End: Climb command handling functions
////////////////////////////////////////////////////////////////////////////////
//	Start: Measure functions
////////////////////////////////////////////////////////////////////////////////

public toggle_measure( id )
{
	//Remove flag for other measure
	new cflags = timer[id][TMR_CFLAGS]
	if( cflags & CF_MEASURE2 ) timer[id][TMR_CFLAGS] = cflags & ~CF_MEASURE2
	
	//Toggle flag for this measure and print message
	timer[id][TMR_CFLAGS] ^= CF_MEASURE
	if( cflags & CF_MEASURE )
		clmsg( id, "Measure disabled." )
	else
		clmsg( id, "Measure enabled, use attack to measure." )
}

public do_measure( id )
{
	new Float:orig[3], Float:look[3], Float:end[3], Float:ret[3]
	new Float:ln_st[3], Float:ln_fn[3]
	
	entity_get_vector( id, EV_VEC_origin, orig )
	entity_get_vector( id, EV_VEC_view_ofs, ret )
	for( new i = 0; i < 3; i++ ) orig[i] += ret[i]
	
	velocity_by_aim( id, 1, ret )
	for( new i = 0; i < 3; i++ ) end[i] = orig[i] + ( ret[i] * 9999 )
	
	trace_line( id, orig, end, ln_st )
	trace_normal( id, orig, end, look )
	
	for( new i = 0; i < 3; i++ ) end[i] = ln_st[i] + ( look[i] * 9999 )
	
	trace_line( id, ln_st, end, ln_fn )
	
	end[0] = ln_st[0] + ( ret[0] * -9999 )	
	end[1] = ln_st[1] + ( ret[1] * -9999 )
	end[2] = ln_st[2]
	
	trace_line( id, ln_st, end, orig )
	
	new msg[101]
	format( msg, 100, "Distance: red %d / blue %d",
		floatround( vector_distance( ln_st, ln_fn ), floatround_ceil ),
		floatround( vector_distance( ln_st, orig ), floatround_ceil ) )
	clmsg( id, msg )
	console_print( id, msg )
	
	message_begin( MSG_ONE, SVC_TEMPENTITY, _, id )
	write_byte( TE_BEAMPOINTS )
	write_coord( floatround( ln_st[0] ) )
	write_coord( floatround( ln_st[1] ) )
	write_coord( floatround( ln_st[2] ) )
	write_coord( floatround( ln_fn[0] ) )
	write_coord( floatround( ln_fn[1] ) )
	write_coord( floatround( ln_fn[2] ) )
	write_short( beam_sprite )	// sprite index
	write_byte( 0 )	// starting frame
	write_byte( 10 )	// frame rate in 0.1's
	write_byte( 100 )	// life in 0.1's
	write_byte( 10 )	// line width in 0.1's
	write_byte( 2 )	// noise amplitude in 0.01's
	write_byte( 255 )	// Red
	write_byte( 0 )	// Green
	write_byte( 0 )	// Blue
	write_byte( 127 )	// brightness
	write_byte( 50 )	// scroll speed in 0.1's
	message_end( )
	
	message_begin( MSG_ONE, SVC_TEMPENTITY, _, id )
	write_byte( TE_BEAMPOINTS )
	write_coord( floatround( ln_st[0] ) )
	write_coord( floatround( ln_st[1] ) )
	write_coord( floatround( ln_st[2] ) )
	write_coord( floatround( orig[0] ) )
	write_coord( floatround( orig[1] ) )
	write_coord( floatround( orig[2] ) )
	write_short( beam_sprite )	// sprite index
	write_byte( 0 )	// starting frame
	write_byte( 10 )	// frame rate in 0.1's
	write_byte( 100 )	// life in 0.1's
	write_byte( 10 )	// line width in 0.1's
	write_byte( 2 )	// noise amplitude in 0.01's
	write_byte( 0 )	// Red
	write_byte( 0 )	// Green
	write_byte( 255 )	// Blue
	write_byte( 127 )	// brightness
	write_byte( 50 )	// scroll speed in 0.1's
	message_end( )
}

public toggle_measure2( id )
{
	//Remove flag for other measure
	new cflags = timer[id][TMR_CFLAGS]
	if( cflags & CF_MEASURE ) timer[id][TMR_CFLAGS] = cflags & ~CF_MEASURE
	
	//Toggle flag for this measure and print message
	timer[id][TMR_CFLAGS] ^= CF_MEASURE2
	if( cflags & CF_MEASURE2 )
		clmsg( id, "Measure 2 disabled." )
	else
		clmsg( id, "Measure 2 enabled, use attack to measure." )
}

public tsk_do_measure2( tskid )
	do_measure2( tskid - TSK_MEASURE2 )

stock do_measure2( id, set = 0 )
{
	new tskid = TSK_MEASURE2 + id
	
	static Float:ln_st_static[33][3]
	
	new Float:orig[3], Float:ret[3], Float:end[3]
	
	entity_get_vector( id, EV_VEC_origin, orig )
	entity_get_vector( id, EV_VEC_view_ofs, ret )
	for( new i = 0; i < 3; i++ ) orig[i] += ret[i]
	
	velocity_by_aim( id, 1, ret )
	
	for( new i = 0; i < 3; i++ ) end[i] = orig[i] + ( ret[i] * 9999 )
	trace_line( id, orig, end, ret )
	
	new Float:ln_st[3] 
	ln_st = ln_st_static[id]
	
	//If first iteration, save point to static and return
	if( !( ln_st[0] || ln_st[0] || ln_st[0] ) )
	{
		ln_st_static[id] = ret
		set_task( 0.1, "tsk_do_measure2", tskid, _, _, "b" )
		return
	}
	
	new Float:ln_fn[3], Float:ln_stmid[3], Float:ln_fnmid[3]
	ln_fn = ret
	
	ln_stmid[0] = ln_st[0]
	ln_stmid[1] = ln_st[1]
	ln_stmid[2] = ln_fn[2]
	
	ln_fnmid[0] = ln_fn[0]
	ln_fnmid[1] = ln_st[1]
	ln_fnmid[2] = ln_fn[2]
	
	new msg[101]
	format( msg, 100, "Distance: r %d / g %d / b %d",
		floatround( vector_distance( ln_st, ln_stmid ), floatround_ceil ),
		floatround( vector_distance( ln_stmid, ln_fnmid ), floatround_ceil ),
		floatround( vector_distance( ln_fnmid, ln_fn ), floatround_ceil ) )
	clmsg( id, msg )
	
	new life = 1, dest = MSG_ONE_UNRELIABLE
	if( set )
	{
		life = 125
		dest = MSG_ONE
		console_print( id, msg )
	}
	
	message_begin( dest, SVC_TEMPENTITY, _, id )
	write_byte( TE_BEAMPOINTS )
	write_coord( floatround( ln_st[0] ) )
	write_coord( floatround( ln_st[1] ) )
	write_coord( floatround( ln_st[2] ) )
	write_coord( floatround( ln_stmid[0] ) )
	write_coord( floatround( ln_stmid[1] ) )
	write_coord( floatround( ln_stmid[2] ) )
	write_short( beam_sprite )	// sprite index
	write_byte( 0 )	// starting frame
	write_byte( 10 )	// frame rate in 0.1's
	write_byte( life )	// life in 0.1's
	write_byte( 10 )	// line width in 0.1's
	write_byte( 2 )	// noise amplitude in 0.01's
	write_byte( 255 )	// Red
	write_byte( 0 )	// Green
	write_byte( 0 )	// Blue
	write_byte( 127 )	// brightness
	write_byte( 50 )	// scroll speed in 0.1's
	message_end( )
	
	message_begin( dest, SVC_TEMPENTITY, _, id )
	write_byte( TE_BEAMPOINTS )
	write_coord( floatround( ln_stmid[0] ) )
	write_coord( floatround( ln_stmid[1] ) )
	write_coord( floatround( ln_stmid[2] ) )
	write_coord( floatround( ln_fnmid[0] ) )
	write_coord( floatround( ln_fnmid[1] ) )
	write_coord( floatround( ln_fnmid[2] ) )
	write_short( beam_sprite )	// sprite index
	write_byte( 0 )	// starting frame
	write_byte( 10 )	// frame rate in 0.1's
	write_byte( life )	// life in 0.1's
	write_byte( 10 )	// line width in 0.1's
	write_byte( 2 )	// noise amplitude in 0.01's
	write_byte( 0 )	// Red
	write_byte( 255 )	// Green
	write_byte( 0 )	// Blue
	write_byte( 127 )	// brightness
	write_byte( 50 )	// scroll speed in 0.1's
	message_end( )
	
	message_begin( dest, SVC_TEMPENTITY, _, id )
	write_byte( TE_BEAMPOINTS )
	write_coord( floatround( ln_fnmid[0] ) )
	write_coord( floatround( ln_fnmid[1] ) )
	write_coord( floatround( ln_fnmid[2] ) )
	write_coord( floatround( ln_fn[0] ) )
	write_coord( floatround( ln_fn[1] ) )
	write_coord( floatround( ln_fn[2] ) )
	write_short( beam_sprite )	// sprite index
	write_byte( 0 )	// starting frame
	write_byte( 10 )	// frame rate in 0.1's
	write_byte( life )	// life in 0.1's
	write_byte( 10 )	// line width in 0.1's
	write_byte( 2 )	// noise amplitude in 0.01's
	write_byte( 0 )	// Red
	write_byte( 0 )	// Green
	write_byte( 255 )	// Blue
	write_byte( 127 )	// brightness
	write_byte( 50 )	// scroll speed in 0.1's
	message_end( )
	
	//Remove task if flag has been removed
	if( task_exists( tskid ) && !( timer[id][TMR_CFLAGS] & CF_MEASURE2 ) ) remove_task( tskid )
	
	//If a second point hasn't been set then end here
	if( !set ) return
	
	//If it gets this far it's the second iteration, /*so disable flag and*/ reset static var
	//timer[id][TMR_CFLAGS] ^= CF_MEASURE2
	ln_st_static[id] = Float:{ 0.0, 0.0, 0.0 }
	if( task_exists( tskid ) ) remove_task( tskid )
}

////////////////////////////////////////////////////////////////////////////////
//	End: Measure functions
////////////////////////////////////////////////////////////////////////////////
//	Start: Blocked/Forwarded default CS commands
////////////////////////////////////////////////////////////////////////////////
public client_kill( id )
{//Block kill, forward to spectate command
	if( get_pcvar_num( p_climb ) )
	{
		spectate( id )
		return PLUGIN_HANDLED
	}
	return PLUGIN_CONTINUE
}

public block_cmd(id){//Block some commands if climb enabled
	if(get_pcvar_num(p_climb))return PLUGIN_HANDLED
	return PLUGIN_CONTINUE
}

public block_cmd2(id){//Used to block fullupdate always
	return PLUGIN_HANDLED
}

public block_jointeam(id){//Block client trying to switch teams
	if(get_pcvar_num(p_climb)&&isct(id))return PLUGIN_HANDLED
	return PLUGIN_CONTINUE
}

public donothing(){//register_clcmd reference this function, but are picked up by the more flexible code in the client_command forward.
	return PLUGIN_CONTINUE
}

public menuteam(id){//Connect choose team menu - force client to select CT
	if(get_pcvar_num(p_climb))client_cmd(id,"slot2")
	return PLUGIN_CONTINUE
}

public menuclass(id){//Force client to choose random model
	if(get_pcvar_num(p_climb)){
		client_cmd(id,"slot5")
	}
	return PLUGIN_CONTINUE
}

////////////////////////////////////////////////////////////////////////////////
//	End: Blocked/Forwarded default CS commands
////////////////////////////////////////////////////////////////////////////////
//	Start: Database functions
////////////////////////////////////////////////////////////////////////////////
public db_init( )
{

	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, 10 )
	get_cvar_string( "climb_db_serverid", DB_SERVER_ID, 15 )

	//SQL_SetAffinity(type)
	DB_TUPLE=SQL_MakeDbTuple(host,user,pass,name)
	
	if( !get_cvar_num( "climb_db_exists" ) )
	{	
		new autoinc[15]="autoincrement"
		if(equal(type,"mysql"))autoinc="auto_increment"
		new query[600]
		
		formatex( query, 599,
				"create table if not exists %splayers (\
					user_id integer primary key %s,\
					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, autoinc )
		SQL_ThreadQuery( DB_TUPLE, "db_generic_handler", query )
		
		formatex( query, 599,
				"create table if not exists %sscores (\
					score_id integer primary key %s,\
					score integer,\
					server_ip char(15),\
					user_id integer,\
					map_name varchar(32),\
					fin_time float,\
					cps integer,\
					gcs integer,\
					boosts integer,\
					wpns integer,\
					server_time_stamp integer)",
			DB_PREFIX, autoinc )
		SQL_ThreadQuery(DB_TUPLE,"db_generic_handler",query)
		
		formatex( query, 599, "create index scores_score on %sscores (score)", DB_PREFIX )
		SQL_ThreadQuery( DB_TUPLE, "db_generic_handler", query )
		
		formatex( query, 599,
				"create table if not exists %ssessions (\
					steam_id char(25) primary key,\
					user_id integer unique)",
			DB_PREFIX )
		SQL_ThreadQuery( DB_TUPLE, "db_generic_handler", query )
		
		set_cvar_num( "climb_db_exists", 1 )
	}
}

public db_generic_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime)
	return query_failed( failstate, error, errnum )

public auto_login(ida[1])
{
	new id=ida[0]
	
	//debug
	new name[33],sid[33],data[2],query[151]
	get_user_name( id, name, 32 )
	get_user_authid( id, sid, 32 )
	//log_amx( "CLIMB: auto_login( %d ) / %s / %s", id, name, sid )

	data[0] = id
	data[1] = 2
	formatex( query, 150,
		"select user_id, steam_id, password from %splayers where steam_id=^"%s^"",
		DB_PREFIX, sid )
	//formatex( data[2], 24, sid)
	SQL_ThreadQuery( DB_TUPLE, "login_handler", query, data, 2 )
	
	//log_amx( "CLIMB: Login Query for id=%d - ^"%s^";", id, query )

	//return client_cmd( id, "login" )
	//return db_login( id )
}
	
public db_login( id )
{
	//log_amx( "CLIMB: Function entry - login( %d )", id )
	if( get_pcvar_num( p_climb ) )
	{
		if( CLIMB_SAVE )
		{
			//log_amx( "CLIMB: Begin login client id=%d.", id )
			//log_amx( "CLIMB: timer[id][TMR_DBUSER]=%d.", timer[id][TMR_DBUSER] )
		
			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[151], data[2] //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, 150,
					"select user_id, steam_id, from %splayers where user_id=^"%s^" and password=^"%s^";",
					DB_PREFIX, user, pass )
				//formatex( data[2], 24, user)
				SQL_ThreadQuery( DB_TUPLE, "login_handler", query, data, 2 )
			}
			else	//Client is logging in with SteamID
			{*/
			new sid[32]
			get_user_authid( id, sid, 31 )
			data[1] = 2
			formatex( query, 150,
				"select user_id, steam_id, password from %splayers where steam_id=^"%s^"",
				DB_PREFIX, sid )
			//formatex( data[2], 24, sid)
			SQL_ThreadQuery( DB_TUPLE, "login_handler", query, data, 2 )
			
			//log_amx( "CLIMB: Login Query for id=%d - ^"%s^";", id, query )
			//}
		}
		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]
	//log_amx( "CLIMB: Function entry - login_handler( %d )", id )
	//If they've disonnected since the login attempt initiated, abort
	if( !is_user_connected( id ) ) return log_amx( "CLIMB: Login Error id=%d - Client no longer exists.", id )
	
	if( SQL_NumResults( query ) < 1 )
	{
		//log_amx( "CLIMB: Login Error id=%d - Not a valid account. 0 result returned", id )
		return client_print( id, print_console, "[Climb] Login Error: Not a valid account." )
	}
	
	if( !query_failed( failstate, error, errnum) )
	{
		//If database steamid doesn't match client steamid, abort
		new db_sid[32], sid[32]
		SQL_ReadResult( query, 1, db_sid, 31 )
		get_user_authid( id, sid, 31 )
		if( !equal( sid, db_sid ) ) return log_amx( "CLIMB: Login Error id=%d - SteamID mismatch.", id )
		
		//Fail if steamid login attempt, and steamid is registered as shared
		if( data[1] == 2 )
		{
			new pass[7]
			SQL_ReadResult( query, 2, pass, 6 )
			if( equali( pass, "shared" ) )
			{
				//log_amx( "CLIMB: Login Error id=%d - Shared SteamID.", id )
				return client_print( id, print_console,\
					"[Climb] Login Error: You are using a shared SteamID.  Please login with a username and password." )
			}
		}
		
		//Everything is ok, add db userid to timer info, display login messages
		timer[id][TMR_DBUSER] = SQL_ReadResult( query, 0 )
		new msg[100]
		formatex( msg, 99, "[Climb] Login: Success - Account: %s", sid )
		//log_amx( "CLIMB: Login Success id=%d", id )
		client_print(id,print_console,msg )
		format( msg, 99, "^x04%s", msg )
		saytext( id, id, msg )
		
		//If they just registered and logged in, save score, else load score
		if( timer[id][TMR_CFLAGS] & CF_JUST_REGD )
		{
			db_save( id )
			timer[id][TMR_CFLAGS] -= CF_JUST_REGD
		}
		else db_load(id)
	}
	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 db_load( id )
{
	new query[351], mapname[33], data[1]
	data[0] = id
	get_mapname( mapname, 32 )

	formatex( query, 350,
		"select s.fin_time, s.cps, s.gcs, g.fin_cnt, s.boosts, s.wpns from %sscores s, (select user_id, count(*) fin_cnt from %sscores where map_name = ^"%s^" group by user_id) g where map_name = ^"%s^" and s.user_id = %d and g.user_id = %d order by score",
		DB_PREFIX, DB_PREFIX, mapname, mapname, timer[id][TMR_DBUSER], timer[id][TMR_DBUSER] )
			
	SQL_ThreadQuery( DB_TUPLE, "db_load_handler", query, data, 1 )
}

public db_load_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime)
{
	new id=data[0]
	if( !query_failed( failstate, error, errnum) )
	{
		new msg[100]
		if(!SQL_NumResults(query))
			msg="^x04No stats available for this account on the current map."
		else
		{
			new mapname[33], Float:fTmp
			get_mapname(mapname,32)
			SQL_ReadResult( query, 0, fTmp )
			timer[id][TMR_BSTTME] = floatround( fTmp * 10 )
			timer[id][TMR_BSTCPS] = SQL_ReadResult( query, 1 )
			timer[id][TMR_BSTGCS] = SQL_ReadResult( query, 2 )
			timer[id][TMR_MAPFIN] = SQL_ReadResult( query, 3 )
			timer[id][TMR_BSTBST] = SQL_ReadResult( query, 4 )			
			timer[id][TMR_BSTWPN] = SQL_ReadResult( query, 5 )
			//timer[id][TMR_CFLAGS]+=SQL_ReadResult(query,5)?CF_BSTSCT:0 //Read cflag preferences from db, maybe this should be setinfos instead
			
			formatex( msg, 99,
				"^x04Stats loaded for %s - %s^t(%d/ %d CP/%d GC/%d Boost)^tCompleted %d",
				mapname, parsetime( timer[id][TMR_BSTTME] ), timer[id][TMR_BSTWPN], timer[id][TMR_BSTCPS],
				timer[id][TMR_BSTGCS], timer[id][TMR_BSTBST], timer[id][TMR_MAPFIN] )
		}
		saytext( id, id, msg )
	}
	else client_print( id, print_chat, "[Climb] DB Read Error: Please notify a server admin." )
	return PLUGIN_HANDLED
}

public db_save( id )
{
	new user_id = timer[id][TMR_DBUSER]
	
	if( user_id < 1 )
	{
		auto_reg( id )
		return PLUGIN_HANDLED
	}
	
	new query[351], name[33], data[2]
	data[0] = id
	get_mapname( name, 32 )
	strtolower( name )
	
	new cntbst = timer[id][TMR_CNTBST]
	new cntcps = timer[id][TMR_CNTCPS]
	new cntwpn = timer[id][TMR_CNTWPN]

	new score = ( ( ( cntbst > 0 ? 1 : 0 ) * 1000000 ) +
		( ( cntcps > 0 ? 1 : 0 ) * 100000 ) +
		( cntwpn * 10000 ) +
		timer[id][TMR_CNTTME] / 10 )
	
	formatex( query, 350,
		"insert into %sscores (server_ip, user_id, map_name, fin_time, cps, gcs, boosts, wpns, score, server_time_stamp) values (^"%s^", %d, ^"%s^", %f, %d, %d, %d, %d, %d, %d)",\
		DB_PREFIX, DB_SERVER_ID, user_id, name, timer[id][TMR_CNTTME] * 0.1, cntcps, timer[id][TMR_CNTGCS], cntbst, cntwpn, score, get_systime() )
	SQL_ThreadQuery(DB_TUPLE,"db_save_handler",query,data,2)
	
	data[1] = 0
	get_user_name( id, name, 32 )
	formatex( query, 350, "update %splayers set alias=^"%s^" where user_id=%d;", DB_PREFIX, name, user_id )
	SQL_ThreadQuery( DB_TUPLE, "db_name_update_handler", query, data, 2 )
	return PLUGIN_HANDLED
}

public db_save_handler( failstate, Handle:query, error[], errnum, data[], size, Float:queuetime )
{
	if( query_failed( failstate, error, errnum ) )
	{
		new id=data[0]
		client_print(id,print_chat,"[Climb] DB Write Error: Could not update your score. Please notify a server admin.")
	}
	
	return PLUGIN_HANDLED
}

public db_name_update_handler( failstate, Handle:query, error[], errnum, data[], size, Float:queuetime )
{
	if( query_failed( failstate, error, errnum ) )
	{
		new id = data[0]

		if( data[1] > 9 )
			client_print(id,print_chat,"[Climb] DB Write Error: Could not update your name. Please notify a server admin.")
		else
		{
			new query[250]
			new data2[2]
			data2[0] = id
			data2[1] = data[1] + 1
			
			
			new name[33]
			get_user_name( id, name, 29 )
			format( name, 32, "%s(%d)", name, data2[1] )
			
			formatex( query, 249,\
				"update %splayers set alias=^"%s^" where user_id=%d;",\
				DB_PREFIX, name, timer[id][TMR_DBUSER] )
			SQL_ThreadQuery(DB_TUPLE,"db_name_update_handler",query,data2,2)
		}
	}

	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 client_print(id,print_console,"[Climb] Registration Error: Can't Register; Stats not enabled.")
	}
	return PLUGIN_HANDLED
}

public auto_reg(id)
{
	new query[100],sid[26],name[33],data[43]
	data[0]=id
	data[1]=2
	get_user_authid(id,sid,25)
	get_user_name(id,name,32)
	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)
	return PLUGIN_HANDLED
}

public reg_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime)
{
	new id=data[0]
	if( !query_failed( failstate, error, errnum) )
	{
		new flag=data[1],user[21],pass[21]
		format(user,20,data[2])
		format(pass,20,data[22])
	
		if(flag==0)client_print(id,print_console,"[Climb] Recorded shared SteamID.")
		else if(flag==1){
			client_print(id,print_console,"[Climb] Registration Successful.")
			
			new cmd[50]
			formatex(cmd,49,"login %s %s",user,pass)
			client_cmd(id,cmd)
		}
		else if(flag==2){
			timer[id][TMR_CFLAGS]+=CF_JUST_REGD
			client_cmd(id,"login")
		}
	}
	else client_print(id,print_chat,"[Climb] DB Registration Error: Please notify a server admin.")
	return PLUGIN_HANDLED
}

public highscores(id)
{
	if( !CLIMB_SAVE ) return clmsg( id, "Stats not enabled.")
	
	new hsurl[150], mapname[33]
	get_pcvar_string( p_stats_hsurl, hsurl, 149 )
	get_mapname( mapname, 32 )
	if( strlen( hsurl ) )
	{
		format( hsurl, 149, hsurl, mapname )
		show_motd( id, hsurl, "High Scores" )
		return PLUGIN_HANDLED
	}
	if( get_systime() - ts_hscore > 10 )
	{
		new query[501], data[1]
		data[0] = id
		
		formatex( query, 500,
			"select distinct p.alias, s.fin_time, s.cps, s.gcs, g.fin_cnt, s.boosts, s.wpns, s.score from %sscores s join %splayers p on s.user_id = p.user_id join (select user_id, min(score) minscore, count(*) fin_cnt from %sscores where map_name=^"%s^" group by user_id, wpns) g on p.user_id=g.user_id where map_name=^"%s^" and s.score=g.minscore order by s.score limit 20",
			DB_PREFIX, DB_PREFIX, DB_PREFIX, mapname, mapname )
		
		SQL_ThreadQuery( DB_TUPLE, "hs_handler", query, data, 1 )
		ts_hscore = get_systime()
	}
	else
	{
		if( has_hscores )
			show_motd( id, HSCORES_PATH, "High Scores" )
		else
		{
			new msg[101]
			format( msg, 100, "^x04[Climb] No stats available for the current map." )
			return saytext( id, id, msg )
		}
	}
	
	return PLUGIN_HANDLED
}

public hs_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime)
{
	if( !query_failed( failstate, error, errnum) )
	{
		new id=data[0],msg[101],num=SQL_NumResults(query)
		if(!num){
			format(msg,100,"^x04[Climb] No stats available for the current map.")
			return saytext(id,id,msg)
		}
		
		has_hscores = true
		
		new fh = fopen( HSCORES_PATH, "w" )
		
		fprintf( fh, "<link rel=stylesheet href=http://ian.cammarata.us/sb><table><tr><td id=a>" )
		fprintf( fh, "<pre>#	Name		Time	Wpn/CP/GC/Boost	Fin</pre>" )
		fprintf( fh, "</td></tr><tr id=b><td></td></tr><tr><td><pre>" )

		new name[NAMELEN+2], line[151], written_len, btime_str[20]
		for( new i = 1; i <= num; i++ )
		{
			SQL_ReadResult( query, 0, name, NAMELEN )
			sb_add_tabs( name, NAMELEN+2 )
			
			
			format( btime_str, 19, "%d/%d/%d/%d",
				SQL_ReadResult( query, 6 ),
				SQL_ReadResult( query, 2 ),
				SQL_ReadResult( query, 3 ),
				SQL_ReadResult( query, 5 )
			)
			sb_add_tabs( btime_str, 19 )
			
			new Float:fTmp
			SQL_ReadResult( query, 1, fTmp )
			
			formatex( line, 150,\
				"%s%d	%s%s	%s	%d%s",
				i % 2 ? NULLSTR : "<div>",
				i, htmlspecialchars( name ),
				parsetime( floatround( fTmp * 10 ) ),
				btime_str,
				SQL_ReadResult( query, 4 ),
				i % 2 ? NULLSTR : "</div>" )
			written_len += strlen( line )
			if( written_len > 1263 ) break
			fprintf( fh, line )
			SQL_NextRow( query )
		}
		
		//1530-150-117=1263
		new cust_msg[33]		
		get_pcvar_string( p_stats_msg, cust_msg, 32 )
		if( !strlen( cust_msg ) ) formatex( cust_msg, 32, "Climb %s", VERSION )
		fprintf( fh, "</pre></td></tr><tr id=d><td></td></tr><tr><td id=e></td></tr><tr>" )
		fprintf( fh, "<td id=a>%s</td></tr></table>", cust_msg )
		fclose( fh )
		
		show_motd( id, HSCORES_PATH, "High Scores" )
	}
	return PLUGIN_HANDLED
}

public climb_dbwho(id,level,cid)
{
	if(cmd_access(id,level,cid,0))
	{
		console_print(id,"^n#  DB ID (<0=Not in DB), Name")
		
		new players[32],count,name[21],id2
		get_players( players, count, "ch" )
		for( new i=0; i<count; i++ )
		{
			id2=players[i]
			get_user_name(id2,name,20)
			console_print(id,"#  %d, %s",timer[id2][TMR_DBUSER],name)
		}
		console_print( id, "%d Players", count )
	}
	return PLUGIN_HANDLED
}

public climb_dbmap( id, level, cid )
{
	if( cmd_access( id, level, cid, 0 ) )
	{
		new mapname[33]
		if( read_argc()>1 )read_argv( 1, mapname, 32 )
		if( !strlen(mapname) )get_mapname( mapname, 32 )
		
		new query[501], data[1]
		data[0]=id
					
		formatex( query, 500,
			"select distinct p.alias, s.score_id, s.fin_time, s.cps, s.gcs, s.boosts, s.wpns from %sscores s join %splayers p on s.user_id = p.user_id join (select user_id, min(score) minscore, count(*) fin_cnt from %sscores where map_name=^"%s^" group by user_id, wpns) g on p.user_id=g.user_id where map_name=^"%s^" and s.score=g.minscore order by s.score limit 20",
			DB_PREFIX, DB_PREFIX, DB_PREFIX, mapname, mapname )
		SQL_ThreadQuery( DB_TUPLE, "climb_dbmap_handler", query, data, 1 )
	}
	return PLUGIN_HANDLED
}

public climb_dbmap_handler( failstate, Handle:query, error[], errnum, data[], size, Float:queuetime )
{
	if( !query_failed( failstate, error, errnum) )
	{
		new id=data[0]
		new count=SQL_NumResults( query )
		if( count )
		{
			new name[21]
			console_print( id, "^n#  Alias, Score ID, Time, cps/gcs/boosts, Scout" )
			for( new i=0; i<count; i++ )
			{
				SQL_ReadResult( query, 0, name, 20 )
				console_print( id, "#  %s, %d, %d, %d/%d/%d, %s",\
					name,\
					SQL_ReadResult( query, 1 ),\
					SQL_ReadResult( query, 2 ),\
					SQL_ReadResult( query, 3 ),\
					SQL_ReadResult( query, 4 ),\
					SQL_ReadResult( query, 5 ),\
					SQL_ReadResult( query, 6 )?"y":"n")
				SQL_NextRow( query )
			}
			console_print( id, "%d Result(s)", count )
		}
		else console_print( id, "Query returned no results." )
	}
	return PLUGIN_HANDLED
}

public climb_dbuser( id, level, cid )
{
	if( cmd_access( id, level, cid, 1 ) )
	{
		new db_user_id[10]
		read_argv( 1, db_user_id, 9 )
		
		new query[250], data[1]
		data[0]=id
		
		formatex( query, 249,\
			"select map_name, score_id, fin_time, cps, gcs, boosts, wpns from %sscores where user_id=%d;",\
			DB_PREFIX, str_to_num( db_user_id ) )
		SQL_ThreadQuery( DB_TUPLE, "climb_dbuser_handler", query, data, 1 )
	}
	return PLUGIN_HANDLED
}

public climb_dbuser_handler( failstate, Handle:query, error[], errnum, data[], size, Float:queuetime )
{
	if( !query_failed( failstate, error, errnum ) )
	{
		new id=data[0]
		new count=SQL_NumResults( query )
		if( count )
		{
			new name[21]
			console_print(id,"^n#  Map, Score ID, Time, cps/gcs/boosts, Scout")
			for( new i=0; i<count; i++ )
			{
				SQL_ReadResult( query, 0, name, 20 )
				console_print( id, "#  %s, %d, %d, %d/%d/%d, %s",\
					name,\
					SQL_ReadResult(query,1),\
					SQL_ReadResult(query,2),\
					SQL_ReadResult(query,3),\
					SQL_ReadResult(query,4),\
					SQL_ReadResult(query,5),\
					SQL_ReadResult(query,6) ? "y" : "n" )
				SQL_NextRow( query )
			}
			console_print( id, "%d Result(s)", count )
		}
		else console_print( id, "Query returned no results.")
	}
	return PLUGIN_HANDLED
}

public climb_dbdelete( id, level, cid )
{
	if(cmd_access(id,level,cid,1))
	{
		new db_user_id[10]
		read_argv( 1, db_user_id, 9 )
		
		new query[250], data[2], del_id
		data[1]=del_id=str_to_num( db_user_id )
		data[0]=id		
		
		if(db_last_del_id!=del_id)
		{//First time show data to be deleted and ask if they're sure.
			formatex( query, 249,\
				"select p.alias, s.map_name, s.fin_time, s.cps, s.gcs, s.boosts, s.wpns from %sscores s, %splayers p where p.user_id=s.user_id and s.score_id=%d;",\
				DB_PREFIX, DB_PREFIX, del_id )
			SQL_ThreadQuery( DB_TUPLE, "climb_dbdelete_verify", query, data, 2 )
		}
		else
		{//Second time execute the delete
			formatex( query, 249,\
				"delete from %sscores where score_id=%d;",\
				DB_PREFIX, del_id )
			SQL_ThreadQuery( DB_TUPLE, "climb_dbdelete_handler", query, data, 2 )
			db_last_del_id=0
		}
	}
	return PLUGIN_HANDLED
}

//For nightly builds
public climb_dbrecalc( id, level, cid )
{
	if( cmd_access( id, level, cid, 0 ) )
		SQL_ThreadQuery( DB_TUPLE, "db_generic_handler",
			"update %sscores set score = ( ( (boosts>0) * 1000000 ) + ( (cps>0) * 100000 ) + ( wpns * 10000 ) + fin_time )",
			DB_PREFIX )
	return PLUGIN_HANDLED
}

public climb_dbdelete_verify( failstate, Handle:query, error[], errnum, data[], size, Float:queuetime )
{
	if( !query_failed( failstate, error, errnum ) )
	{
		new id=data[0]
		new del_id=data[1]
		new count=SQL_NumResults( query )
		if( count )
		{
			new name[21],map[33]
			console_print( id, "^n#  Name, Map, Time, cps/gcs/boosts, Scout" )

			SQL_ReadResult( query, 0, name, 20 )
			SQL_ReadResult( query, 1, map, 32 )
			console_print( id, "#  %s, %s, %d, %d/%d/%d, %s",\
				name,\
				map,\
				SQL_ReadResult(query,2),\
				SQL_ReadResult(query,3),\
				SQL_ReadResult(query,4),\
				SQL_ReadResult(query,5),\
				SQL_ReadResult(query,6) ? "y" : "n" )

			console_print( id, "%d Result(s)", count )//debug
			console_print( id, "If you're sure you want to delete this record, execute the command again." )
			db_last_del_id=del_id
		}
		else console_print( id, "No record exists with given DB Score ID." )
	}
	return PLUGIN_HANDLED
}

public climb_dbdelete_handler( failstate, Handle:query, error[], errnum, data[], size, Float:queuetime )
{
	new id=data[0]
	if( !query_failed( failstate, error, errnum ) )
		console_print( id, "Record has been deleted." )
	else console_print( id, "Record could not be deleted." )
	return PLUGIN_HANDLED
}

public query_failed( failstate, error[], errnum )
{
	if( failstate == TQUERY_CONNECT_FAILED )
	{
		log_amx( "Climb: Couldn't connect to database: %s", error )
		return 1
	}
	else if( failstate == TQUERY_QUERY_FAILED )
	{
		log_amx( "Climb: Query failed: %s", error )
		return 1
	}
   
	if( errnum )
	{
		log_amx( "Climb: Query Error: %s", error )
		return 1
	}
	
	return 0
}
////////////////////////////////////////////////////////////////////////////////
//	End: Database functions
////////////////////////////////////////////////////////////////////////////////
public rotwtf( string[], out_len )
{//Simple Password Encryption
	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_end()
{
	if( CLIMB_SAVE )SQL_FreeHandle( DB_TUPLE )
	if( get_pcvar_num( p_auto ) )
	{
		set_pcvar_num( p_climb, 0 )
		set_pcvar_num( p_teambalance, TEAM_BALANCE_OLD )
		set_pcvar_num( p_limitteams, LIMIT_TEAMS_OLD )
	}
	return PLUGIN_CONTINUE
}

Contact
ViewVC Help
Powered by ViewVC 1.0.4