[Half-Life AMXX] / compiled / climb_2_0_a3 / amxmodx / scripting / climb.sma Repository:
ViewVC logotype

View of /compiled/climb_2_0_a3/amxmodx/scripting/climb.sma

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (download) (annotate)
Tue Oct 30 09:08:11 2007 UTC (16 years, 6 months ago) by ian
File size: 78981 byte(s)
/*
Climb v2.0a3
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 General Public License as  published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

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

You should have received a copy of the GNU General Public License along with this program; go to http://www.opensource.org/licenses/gpl-license.php

To do:
  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
  Overhaul high score system

Climb v2.0a3
Created by Ian (Juan) Cammarata
http://ian.cammarata.us
AMXX 1.76c
6/21/2007 3:32:24 PM

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)

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

Notes:
	Enjoy!

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

	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>

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

new  MAXPLAYERS

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

//Client flags arrays
new Float:origins[32][48],timer[32][13],time_stamps[32][2],Float:post_think_vel[32][3]
#define ORIG_UNGC 40
#define ORIG_PAUS 44

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

//Other stuff
new hooked[32],hp=100
new sfactions[SF_MAX][50],sfcount=0
new score_ts,hscore_ts
new dyn_spawn_ids[32],dyn_spawn_count=-1,Float:spawn_tp_orig[3]
new spec_ids[32][32]
new bool:sclip[32]
//Bhop fix vars
new func_doors[150][3],door_count=0,bhop_failid[32],bool:bhop_fail[32]

//Cvar Pointers
new p_climb,p_webmod,p_boost,p_cpprice
new p_msg_r,p_msg_g,p_msg_b,p_msg_x,p_msg_y
new p_sounds,p_render
new p_ip_internal,p_ip_external,p_port
new p_stats_path,p_stats_url,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_startmoney

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

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

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

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

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

//==============================================================================
//	Start: Init forwards
//==============================================================================
public plugin_init(){
	MAXPLAYERS=get_maxplayers()
	register_plugin("Climb",VERSION,"Ian Cammarata")
	register_cvar("climb_version",VERSION,FCVAR_SERVER)
	
	p_climb=  register_cvar("climb","0",FCVAR_SERVER)
	p_boost=  register_cvar("climb_boost","1")
	p_cpprice=register_cvar("climb_cpprice","0")
	
	register_cvar("climb_save","1")
	
	register_cvar("climb_db_type","sqlite")
	register_cvar("climb_db_host","127.0.0.1")
	register_cvar("climb_db_user","")
	register_cvar("climb_db_pass","")
	register_cvar("climb_db_name","climb")
	register_cvar("climb_db_prefix","climb_")
	
	p_msg_r=register_cvar("climb_msg_r","0")
	p_msg_g=register_cvar("climb_msg_g","150")
	p_msg_b=register_cvar("climb_msg_b","250")
	p_msg_x=register_cvar("climb_msg_x","0.05")
	p_msg_y=register_cvar("climb_msg_y","0.5")
	
	p_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_ip_internal=register_cvar("ip_internal","localhost")
	p_ip_external=register_cvar("ip_external","localhost")
	p_port=get_cvar_pointer("port")
	
	p_webmod=register_cvar("climb_webmod","0")
	p_stats_path=  register_cvar("climb_stats_path","")
	p_stats_url=   register_cvar("climb_stats_url","")
	p_stats_hsurl= register_cvar("climb_stats_hsurl","") //Use %s in place of map name
	p_stats_msg=   register_cvar("climb_stats_msg","")
	
	p_allow_spectators=get_cvar_pointer("allow_spectators")
	p_startmoney=  register_cvar("climb_startmoney","1337")

//General Client Commands
	//These commands get blocked always.
	register_clcmd("fullupdate","block_cmd2")
	
	//These commands get blocked when climb is enabled.
	register_clcmd("chooseteam","spectate")
	register_clcmd("buy","block_cmd")
	register_clcmd("buyammo1","block_cmd")
	register_clcmd("buyammo2","block_cmd")
	register_clcmd("buyequip","block_cmd")
	
	//register_clcmd("drop","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","login",_,"- (Console Only) Login to stats account.")
	
	//Admin Commands
	register_clcmd("amx_goto","goto_player")
	
	//Climb Flashlight
	register_impulse(100,"tog_flight")
	register_clcmd("nightvision","tog_flight")
	//For scoreboard backend
	//register_srvcmd("amx_climb_sbrefresh","htmlscoreboard")
	
	//Events
	register_event("DeathMsg","death_msg","a")
	register_event("ResetHUD","spawned","b")
	register_event("Health","damage","b")
	register_event("ShowMenu","menuclass","b","4&CT_Select","4&Terrorist_Select")
	register_event("ShowMenu","menuteam","b","4&Team_Select_Spect","4&Team_Select","4&IG_Team_Select")
	
	//Init DB
	if(get_cvar_num("climb_save"))climb_save=true
	if(climb_save)db_init()
	
	//Init anti flood time stamps
	score_ts=get_systime()
	hscore_ts=get_systime()
	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,"updatebot",_,_,_,"b")
	
	//register_dictionary("climb.txt")
	
	//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){
	static last_ent,bool:first_run=true
	new class[31],key[31],val[31]
	if(first_run){
		new id
		for(new i=0;i<32;i++){
			id=create_entity("info_player_start")
			if(id>0){
				dyn_spawn_count++
				dyn_spawn_ids[dyn_spawn_count]=id
				entity_set_int(id,EV_INT_iuser1,1)
			}
		}
		first_run=false
	}
	copy_keyvalue(class,30,key,30,val,30)
	if(equal(class,"func_door")){
		if(ent!=last_ent){
			if(func_doors[door_count][0]>0)door_count++
			func_doors[door_count][0]=ent
		}
		if(equal(key,"speed"))func_doors[door_count][1]=str_to_num(val)
		if(equal(key,"dmg")){
			func_doors[door_count][0]=0
			/*if(str_to_num(val)<0){
					DispatchKeyValue("movesnd","0")
					DispatchKeyValue("stopsnd","0")
			}*/
		}
		if(equal(key,"angles")){
			//new angles[3][4]
			new angles[5]
			parse(val,angles,4)
			func_doors[door_count][2]=str_to_num(angles)
		}
		last_ent=ent
	}
	//func_doors[x][id,speed,x]
	return PLUGIN_CONTINUE
}

public plugin_cfg(){
	new ent,ent2,tmpstr[32],Float:tmpflt
	new bool:has_start_button=false,Float:sb_orig[3]
	
	//Look for kz start button
	ent=find_ent_by_class(-1,"func_button")
	while(ent>0&&!has_start_button){
		entity_get_string(ent,EV_SZ_target,tmpstr,32)
		if(equal(tmpstr,"counter_start")||equal(tmpstr,"clockstartbutton")||equal(tmpstr,"firsttimerelay")){
			has_start_button=true
			get_brush_entity_origin(ent,sb_orig)
		}
		ent=find_ent_by_class(ent,"func_button")
	}
	
	if(has_start_button||get_pcvar_num(p_climb)){
		set_cvar_num("sv_restartround",1)//reset timer built into map
		set_cvar_num("sv_gravity",800)
		set_cvar_num("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 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,31)
				if(strlen(tmpstr)){
					ent2=find_ent_by_target(-1,tmpstr)
					remove_entity(ent2)
				}
				remove_entity(ent)
			}
			ent=find_ent_by_class(ent,"func_door")
		}
		
		//Disable climb auto heal if there is a neg dmg trigger hurt
		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(hp<0)hp=511
		
		if(get_pcvar_num(p_water_nodraw)){
			ent=find_ent_by_class(-1,"func_water")
			while(ent>0){//Set func_water ents to no draw, helps many peoples FPS
				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")){
				remove_entity(ent)
			}
			ent=find_ent_by_class(ent,"ambient_generic")
		}
		
		//Count original map spawns and remove extra dyn created spawns
		new map_spawns[32][2],map_spawns_count,Float:orig[3]
		ent=find_ent_by_class(-1,"info_player_start")
		while(ent>0&&map_spawns_count<32){
			if(entity_get_int(ent,EV_INT_iuser1)!=1){
				entity_get_vector(ent,EV_VEC_origin,orig)
				map_spawns[map_spawns_count][0]=floatround(vector_distance(orig,sb_orig))
				map_spawns[map_spawns_count][1]=ent
				map_spawns_count++
				while(dyn_spawn_count>=0){
					entity_get_string(dyn_spawn_ids[dyn_spawn_count],EV_SZ_classname,tmpstr,31)
					if(equal(tmpstr,"info_player_start")&&entity_get_int(dyn_spawn_ids[dyn_spawn_count],EV_INT_iuser1)==1){
						remove_entity(dyn_spawn_ids[dyn_spawn_count])
						dyn_spawn_count--
						break
					}
					else dyn_spawn_count--
				}
			}
			ent=find_ent_by_class(ent,"info_player_start")
		}
		
		SortStrings(map_spawns,map_spawns_count)
		entity_get_vector(map_spawns[0][1],EV_VEC_origin,spawn_tp_orig)
		entity_set_vector(map_spawns[0][1],EV_VEC_origin,Float:{0,0,0})
		for(new i=5;i<map_spawns_count;i++)//normal i=5
			entity_set_vector(map_spawns[i][1],EV_VEC_origin,Float:{0,0,0})
	}
	else{//if not kz map remove dyn created spanwns
		while(dyn_spawn_count>=0){
			ent=dyn_spawn_ids[dyn_spawn_count]
			entity_get_string(ent,EV_SZ_classname,tmpstr,31)
			//entity_get_string(dyn_spawn_ids[i],EV_SZ_classname,tmpstr,31)
			if(equal(tmpstr,"info_player_start")&&entity_get_int(ent,EV_INT_iuser1)==1)remove_entity(ent)
			dyn_spawn_count--
		}
	}
	return PLUGIN_CONTINUE
}
//==============================================================================
//	End: Init forwards
//==============================================================================
public client_putinserver(id){
	if(get_pcvar_num(p_climb)&&!is_user_bot(id)){
		//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-1]=originssave[i]
				timer[id-1]=timersave[i]
				return PLUGIN_CONTINUE
			}
		
		timer[id-1][TMR_CFLAGS]+=CF_STOP
		
		new ida[1]
		ida[0]=id
		
		if(climb_save&&timer[id-1][TMR_DBUSER]<1)
			set_task(5.0,"auto_login",0,ida,1)//auto login
		
		set_task(20.0,"connect_advert",0,ida,1)
	}
	return PLUGIN_CONTINUE
}

public connect_advert(ida[1]){
	new id=ida[0]
	new msg[51]
	//formatex(msg,50,"^x04 ^t^t^t^t^t^t^t%L v%s %L:",id,"ADVERT",VERSION,id,"BY")
	formatex(msg,50,"^x04 ^t^t^t^t^t^t^tThis server is using Climb v v%s by:",VERSION)
	saytext(id,id,msg)
	saytext(id,id,"^x04 ^t^t^t^t^t^t^tIan (Juan) Cammarata (http://ian.cammarata.us)")
}

public 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]=""		
		
		steamid[savepos]=saveid//save steamid to position reference
		originssave[savepos]=origins[id-1]//save origins 
		if(timer[id-1][TMR_CFLAGS]&CF_START)change_status(id,CF_PAUSE)//Pause if running
		timersave[savepos]=timer[id-1]//save timer array
		
		//clear data for new client in that slot
		for(new i=0;i<48;i++)origins[id-1][i]=0.0
		for(new i=0;i<11;i++)timer[id-1][i]=0
		hooked[id]=0
		sortcssb()//Update frags to reorder scoreboard
	}
	return PLUGIN_CONTINUE
}
public check(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		new cpprice=get_pcvar_num(p_cpprice)
		if(cpprice>0){
			new cash=cs_get_user_money(id)
			if(cpprice>cash){
				clmsg(id,"You don't have enough cash for more checkpoints.")
				client_print(id,print_chat,"You don't have enough cash for more checkpoints.")
				return PLUGIN_HANDLED
			}
			cs_set_user_money(id,cash-cpprice)
		}
		if(!hooked[id]){
			new Float:vel[3]
			entity_get_vector(id,EV_VEC_velocity,vel)
			if(vel[2]>=0){
				new Float:coords[3]
				entity_get_vector(id,EV_VEC_origin,coords)
				if(coords[0]||coords[1]||coords[2]){
					for(new i=39;i>3;i--)origins[id-1][i]=origins[id-1][i-4]
					for(new i=0;i<3;i++)origins[id-1][i]=coords[i]
					origins[id-1][3]=entity_get_float(id, EV_FL_gravity)
					new msg[100]="Checkpoint saved."
					if(timer[id-1][TMR_CFLAGS]&CF_START){
						timer[id-1][TMR_CPSCNT]++
						formatex(msg,99,"Checkpoint saved. (%d CPS/ %d GCS/ %d Boosts)",timer[id-1][TMR_CPSCNT],timer[id-1][TMR_GCSCNT],timer[id-1][TMR_BOOSTS])
					}
					clmsg(id,msg)
				}
				else clmsg(id,"Can not save checkpoint at current location.")
			}
			else clmsg(id,"You can't make checkpoints while falling.")
		}
		else clmsg(id,"You can't make checkpoints while using hook.")
	}
	return PLUGIN_HANDLED
}
public gocheck(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		if(origins[id-1][0]||origins[id-1][1]||origins[id-1][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-1][ORIG_UNGC+i]=coords[i]
			origins[id-1][ORIG_UNGC+3]=entity_get_float(id,EV_FL_gravity)
		//Do gocheck
			for(new i=0;i<3;i++)coords[i]=origins[id-1][i]
			entity_set_float(id,EV_FL_gravity,origins[id-1][3])
			teleport(id,coords)
			new msg[100]="Checkpoint restored."
			if(timer[id-1][TMR_CFLAGS]&CF_START){
				timer[id-1][TMR_GCSCNT]++
				formatex(msg,99,"Checkpoint restored. (%d CPS/ %d GCS/ %d Boosts)",timer[id-1][TMR_CPSCNT],timer[id-1][TMR_GCSCNT],timer[id-1][TMR_BOOSTS])
			}
			clmsg(id,msg)
		}
		else{
			clmsg(id,"You must make a checkpoint first.")
			return 0
		}
	}
	return 1
}
public ungocheck(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		if(origins[id-1][ORIG_UNGC]||origins[id-1][ORIG_UNGC+1]||origins[id-1][ORIG_UNGC+2]){
			new Float:coords[3]
			for(new i=0;i<3;i++)coords[i]=origins[id-1][ORIG_UNGC+i]
			teleport(id,coords)
			entity_set_float(id,EV_FL_gravity,origins[id-1][ORIG_UNGC+3])
			timer[id-1][TMR_GCSCNT]++
			clmsg(id,"You have been ungochecked.")
		}
		else clmsg(id,"Cannot ungocheck.")
	}
}
public stuck(id){
	if(get_pcvar_num(p_climb)&&isalive(id)&&notpaused(id)){
		if(origins[id-1][4]||origins[id-1][5]||origins[id-1][6]){
			new Float:coords[3]
			for(new i=0;i<36;i++) origins[id-1][i]=origins[id-1][i+4]
			for(new i=0;i<3;i++) coords[i]=origins[id-1][i]
			entity_set_float(id,EV_FL_gravity,origins[id-1][3])
			teleport(id,coords)
			new msg[100]="Previous checkpoint restored."
			if(timer[id-1][TMR_CFLAGS]&CF_START){
				timer[id-1][TMR_CPSCNT]--
				formatex(msg,99,"Previous checkpoint restored. (%d CPS/ %d GCS/ %d Boosts)",timer[id-1][TMR_CPSCNT],timer[id-1][TMR_GCSCNT],timer[id-1][TMR_BOOSTS])
			}
			clmsg(id,msg)
			new cpprice=get_pcvar_num(p_cpprice)
			if(cpprice>0){
				new cash=cs_get_user_money(id)
				cs_set_user_money(id,cash+cpprice)
			}
		}
		else clmsg(id,"You have no previous checkpoints remaining.")
	}
	return PLUGIN_HANDLED
}

public change_status(id,newstat){
	new cflags=timer[id-1][TMR_CFLAGS]
	if(cflags&CF_STOP)timer[id-1][TMR_CFLAGS]-=CF_STOP
	else if(cflags&CF_START)timer[id-1][TMR_CFLAGS]-=CF_START
	else if(cflags&CF_PAUSE)timer[id-1][TMR_CFLAGS]-=CF_PAUSE
	if(newstat&CF_PAUSE){
		if(cflags&CF_PAUSE){
			timer[id-1][TMR_CFLAGS]+=CF_START
			timer[id-1][TMR_STARTD]=get_systime()-timer[id-1][TMR_STARTD]
			set_entity_flags(id,FL_FROZEN,0)
			unsolid(id)
			entity_set_float(id,EV_FL_gravity,origins[id-1][ORIG_PAUS+3])
			clmsg(id,"UNPAUSED")
			sfexec(id,2)
		}
		else if(cflags&CF_START){
			timer[id-1][TMR_STARTD]=get_systime()-timer[id-1][TMR_STARTD]
			
			cl_pause(id)
			timer[id-1][TMR_CFLAGS]+=CF_PAUSE
		}
		else{
			clmsg(id,"You must start the timer before you can pause.")
			timer[id-1][TMR_CFLAGS]=cflags
		}
	}
	else timer[id-1][TMR_CFLAGS]+=newstat
	return PLUGIN_HANDLED
}
public cl_pause(id){
	unsolid(id)
	set_rendering(id,kRenderFxGlowShell,0,0,255,kRenderTransColor,1)
	
	entity_set_float(id,EV_FL_gravity,0.0)
	set_entity_flags(id,FL_FROZEN,1)
	clmsg(id,"PAUSED - say '/unpause' to resume.")
	return PLUGIN_HANDLED
}

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

public death_msg(){//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)
		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_user_alive(id)||!isct(id))
		return PLUGIN_HANDLED
	cs_user_spawn(id)
	return PLUGIN_HANDLED
}

public frespawn(id){
	if(is_user_alive(id))teleport(id,spawn_tp_orig)
	else if(check_timeout(id,get_systime(),time_stamps[id-1][TS_SPAWN],2)){
		if(get_user_team(id)==3)cs_set_user_team(id,2)
		cs_user_spawn(id)
	}
	return PLUGIN_HANDLED
}

public spawned(id){//resetHUD
	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(get_user_msgid("StatusIcon"),2)//Block buy menu
			if(is_user_alive(id)){
				if(get_user_team(id)==3)cs_set_user_team(id,2)//Set them to CT if they're a spectator
				heal(id)
				sfexec(id,1)//Execute commands from start/finish config
				if(get_user_flags(id)&ADMIN_SLAY)cs_set_user_scoreattrib(id,4)//Show admin as VIP on Scoreboard.
				
				if(timer[id-1][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 primary spawn position or cp
				else if(!(origins[id-1][0]||origins[id-1][1]||origins[id-1][2])&&(spawn_tp_orig[0]||spawn_tp_orig[1]||spawn_tp_orig[2]))teleport(id,spawn_tp_orig)
				else gocheck(id)
			}
		}
		sortcssb()
	}
	else set_msg_block(get_user_msgid("StatusIcon"),0)
	return PLUGIN_CONTINUE
}

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){
		if(get_playersnum()<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(get_playersnum()==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,0.0)
			entity_set_vector(id,EV_VEC_origin,Float:{999999,999999,999999})
			call_think(id)
		}
	}
	return PLUGIN_HANDLED
}

public check_timeout(id,time1,time2,freq){
	if(time1<time2)return 1
	new dif=time1-time2
	if(dif<freq){
		new msg[76]
		format(msg,75,"You must wait %d more seconds to use this command again.",freq-dif)
		if(id>0)clmsg(id,msg)
		return 0
	}
	return 1
}
public change_boost(id,newboost){	//Change Boost flags
	if(!(cvar_enabled(id,p_boost)&&isalive(id))&&notpaused(id))return PLUGIN_HANDLED
	new msg[151],rmflag,cflags=timer[id-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,(cflags&CF_START?getusertime(id):get_systime()),time_stamps[id-1][TS_BOOST],15)){
		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_BOOSTS]++
		}
		if(newboost==CF_SUPER_JUMP)format(msg,150,"%sSuper Jump Enabled.",msg)
		if(newboost==CF_DOUBLE_JUMP)format(msg,150,"%sDouble Jump Enabled.",msg)
		timer[id-1][TMR_CFLAGS]+=newboost
	}
	if(strlen(msg))clmsg(id,msg)
	return PLUGIN_HANDLED
}
public solid_boost_timer(ida[]){	//Task to auto disable solid boost after timeout
	//if(timer[id-1][TMR_CFLAGS]&CF_SOLID)solid_boost(id) //Shouldn't need the if statement
	change_boost(ida[0],CF_SOLID)
	return PLUGIN_HANDLED
}
public server_frame(){//Semi-clip
	if(get_pcvar_num(p_climb)){
		new players[32],num,i,j,Float:c1[3],Float:c2[3]
		new Float:c1z[3],Float:c2z[3]
		new id_i,id_j,cflags_i,cflags_j
		get_players(players,num,"ac")
		//new Float:c1d[3],Float:c2d[3],xyadd,zadd
		for(i=0;i<num;i++){
			id_i=players[i]
			cflags_i=timer[id_i-1][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-1][TMR_CFLAGS]
					if(!(cflags_j&CF_SOLID)&&i!=j){
						entity_get_vector(id_j,EV_VEC_origin,c2)
						//entity_get_vector(players[j],EV_VEC_velocity,c2d)
						c2z[2]=c2[2]
						c2[2]=0.0
						if(vector_distance(c1,c2)<90&&vector_distance(c1z,c2z)<110){
							if(!(cflags_i&CF_SOLID&&cflags_j&CF_SOLID)){//&&vector_distance(c1,c2)>25&&vector_distance(c1z,c2z)>30)){
								unsolid(id_i)
								unsolid(id_j)
								j=num
							}
						}
					}
					if(j+1==num)solid(id_i)
				}
			}
		}
	}
	return PLUGIN_CONTINUE
}
public client_PreThink(id){
	if(!get_pcvar_num(p_climb)||is_user_bot(id))return PLUGIN_CONTINUE
	
	if(is_user_alive(id)){
		if(sclip[id-1])entity_set_int(id,EV_INT_solid,SOLID_BBOX)
		if(get_user_button(id)&IN_USE&&!(get_user_oldbutton(id)&IN_USE)){//Detect button use. Replicated from HL SDK
			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])
	      }
			}
		}
		if(get_pcvar_num(p_boost)){//Double and super jump boosts
			new cflags=timer[id-1][TMR_CFLAGS],bool:onground=false
			if(get_entity_flags(id)&FL_ONGROUND)onground=true
			if(get_user_button(id)&IN_JUMP){
				if(cflags&CF_SUPER_JUMP&&onground){
					entity_get_vector(id,EV_VEC_velocity,post_think_vel[id-1])
					if(post_think_vel[id-1][0]!=0.0||post_think_vel[id-1][1]!=0.0){
						velocity_by_aim(id,400,post_think_vel[id-1])
						if(post_think_vel[id-1][2]<250.0)post_think_vel[id-1][2]=250.0
						if(post_think_vel[id-1][2]>350.0)post_think_vel[id-1][2]=350.0
					}
					else post_think_vel[id-1]=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-1])
						if(is_finished(id)||post_think_vel[id-1][2]>-500)post_think_vel[id-1][2]=250.0
						else post_think_vel[id-1]=Float:{0.0,0.0,0.0}
					}
				}
			}
		}
	}
	return PLUGIN_CONTINUE
}
public client_PostThink(id){
	if(!(get_pcvar_num(p_climb)&&get_pcvar_num(p_boost)&&is_user_alive(id))||is_user_bot(id))return PLUGIN_CONTINUE
	if(sclip[id-1])entity_set_int(id,EV_INT_solid,SOLID_NOT)
	if(_:get_distance(_:post_think_vel[id-1],{0,0,0})){
		entity_set_vector(id,EV_VEC_velocity,post_think_vel[id-1])
		post_think_vel[id-1]=Float:{0.0,0.0,0.0}
		if(timer[id-1][TMR_CFLAGS]&CF_START){
			timer[id-1][TMR_BOOSTS]++	//Increment client boost count
			time_stamps[id-1][TS_BOOST]=getusertime(id) //Timeout based on timer so they can't pause to avoid timeout
			change_boost(id,CF_NULL)
		}
		else if(!is_finished(id)){
			time_stamps[id-1][TS_BOOST]=get_systime()
			change_boost(id,CF_NULL)
		}
	}
	
	//Store pause data
	if(!(timer[id-1][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-1][ORIG_PAUS+i]=coords[i]
		origins[id-1][ORIG_PAUS+3]=entity_get_float(id,EV_FL_gravity)
	}
	return PLUGIN_CONTINUE
}

public button_used(id,button){
	new targ[32]
	entity_get_string(button,EV_SZ_target,targ,32)
//Timer Start
	if((!(timer[id-1][TMR_CFLAGS]&CF_START)||check_timeout(id,get_systime(),time_stamps[id-1][TS_SPAWN],2))&&(equal(targ,"counter_start")||equal(targ,"clockstartbutton")||equal(targ,"firsttimerelay"))){
		new Float:orig[3],Float:view[3],bool:need_respawn=true
		if(timer[id-1][TMR_CFLAGS]==CF_START)need_respawn=false
		for(new i=0;i<48;i++)origins[id-1][i]=0.0//Erase checkpoints
	//Set all associated variables
		timer[id-1][TMR_STARTD]=get_systime()
		change_status(id,CF_START)
		timer[id-1][TMR_CPSCNT]=0
		timer[id-1][TMR_GCSCNT]=0
		timer[id-1][TMR_BOOSTS]=0
	//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)
		}
		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")
		}
	//Disable Boosts
		change_boost(id,CF_NULL)
		time_stamps[id-1][TS_BOOST]=0
		clmsg(id,"Timer started. Go Go Go!!!")
		//client_print(id,print_chat,"Timer started. Go Go Go!!!")
		sortcssb()//Update frags to reorder scoreboard
	//If stats save enabled warn unregistered clients to register
		if(climb_save)regwarn(id)
	//Execute commands from start/finish config
		sfexec(id,2)
		time_stamps[id-1][TS_SPAWN]=get_systime()
	}
//Timer Stop
	else if(equal(targ,"counter_off")||equal(targ,"clockstopbutton")||equal(targ,"clockstop")){
		if(timer[id-1][TMR_CFLAGS]&CF_START){
		//Set client variables
			timer[id-1][TMR_FINISH]=get_systime()
			change_status(id,CF_STOP)
			timer[id-1][TMR_SESFIN]++
			timer[id-1][TMR_MAPFIN]++
			sortcssb()//Update frags to reorder scoreboard
		//Announce to client
			if(get_pcvar_num(p_sounds))client_cmd(0,"spk woop")
			clmsg(id,"Congratulations 1337 climber.")
			//client_print(id,print_chat,"Congratulations 1337 climber.")
			new name[32],msg[21]
			formatex(msg,sizeof(msg)-1,"%s^t%s",getuserstatus(id),parsetime(getusertime(id)))
			//clmsg(id,msg)
			//client_print(id,print_chat,msg)
		//Announce to all
			get_user_name(id,name,32)
			client_print(0,print_chat,"%s^t%s^t(%d CPS/ %d GCS/ %d Boosts)^tCompleted (%d, %d)",name,msg,timer[id-1][TMR_CPSCNT],timer[id-1][TMR_GCSCNT],timer[id-1][TMR_BOOSTS],timer[id-1][TMR_SESFIN],timer[id-1][TMR_MAPFIN])
		//If new record
			if(getusertime(id)<timer[id-1][TMR_BSTTME]||timer[id-1][TMR_BSTTME]==0){
				timer[id-1][TMR_BSTTME]=getusertime(id)
				timer[id-1][TMR_BSTCPS]=timer[id-1][TMR_CPSCNT]
				timer[id-1][TMR_BSTGCS]=timer[id-1][TMR_GCSCNT]
			}
		//Execute commands from start/finish config
			sfexec(id,2)
		//If stats enabled, warn unregistered clients to register, or save for registered clients
			if(climb_save)
				if(!regwarn(id))
					db_save(id)
		}
		else if(timer[id-1][TMR_CFLAGS]&CF_STOP&&timer[id-1][TMR_SESFIN]==0){
			//change_status(id,CF_FINISH)
			timer[id-1][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-1][TMR_CFLAGS]
	/*switch(timer[id-1][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-1][TMR_CFLAGS]&CF_FINISH)clstat="fn"
	//if(equal(clstat,"ns")&&(get_user_flags(id)&ADMIN_RESERVATION||timer[id-1][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,get_user_msgid("TeamInfo"))
		write_byte(id)
		write_string("CT")
		message_end()
	}
	message_begin(MSG_ALL,get_user_msgid("TeamInfo"))
	write_byte(find_player("i"))
	write_string("SPECTATOR")
	message_end()
	return 1
}

//Returns array of client id's sorted by climb time
public get_players_ordered(players[32],&num){
	get_players(players,num,"ch")
	//Insertion Sort clients by finished time
	for(new i=1;i<num;i++){
		new j=i,tmp
		if(timer[players[i]-1][TMR_BSTTME]>0){
			tmp=players[i]
			while(j>0&&(timer[players[j-1]-1][TMR_BSTTME]==0?18000:timer[players[j-1]-1][TMR_BSTTME])>timer[tmp-1][TMR_BSTTME]){
				players[j]=players[j-1]
				j--
			}
			players[j]=tmp
		}
	}
	
	//2nd Insertion Sort clients by climbing time
	for(new i=1;i<num;i++){
		new j=i,tmp
		if(getusertime(players[i])>0){
			tmp=players[i]
			while(j>0&&(getusertime(players[j-1])==0?18000:getusertime(players[j-1]))>getusertime(tmp)&&timer[players[j-1]-1][TMR_BSTTME]==timer[tmp-1][TMR_BSTTME]){
				players[j]=players[j-1]
				j--
			}
			players[j]=tmp
		}
	}
	return 1
}

//Make pathname for scoreboard html files
public pathname(filename[]){
	new path[100]
	get_pcvar_string(p_stats_path,path,79)
	format(path,99,"%s%s",path,filename)
	return path
}

public get_limit(){
	if(get_pcvar_num(p_webmod))
		return 0
	new url[100]
	get_pcvar_string(p_stats_url,url,99)
	if(strlen(url))
		return 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)
	}
	else if(strlen(url)){
		format(url,99,"%sclimb_scores.html",url)
	}
	return url
}

public climbscores(id){//Show scoreboard
	if(get_pcvar_num(p_climb)){
		new fpn[100],bool:limit=true,cust_msg[129]
		get_pcvar_string(p_stats_msg,cust_msg,128)
		limit=bool:get_limit()
		fpn=pathname("climb_scores.html")
		if(get_systime()-score_ts>2){
			new fh=fopen(fpn,"w")
			fprintf(fh,"<html><head>%s<link rel=stylesheet href=http://ian.cammarata.us/climb/sb.css></head><body><table><tr>",limit?"":"<meta http-equiv=pragma content=no-cache>")
			fprintf(fh,"<th height=1%%>Reg</th><th>#</th><th width=28%%>Name</th><th width=15%%>Status</th><th width=8%%>Time</th><th width=6%%>CP#</th><th width=6%%>GC#</th>")
			fprintf(fh,"<th width=8%%>Best</th><th width=6%%>CP#</th><th width=6%%>GC#</th><th width=10%%>Completed</th></tr><tr><td class=st colspan=11></td></tr>")
			//99+144+134=377
			new line[251],name[33],players[32],num,tid,writen_len
			get_players_ordered(players,num)
			for(new i=1;i<=num;i++){
				tid=players[i-1]
				//if(is_user_connected(tid)){//removed hltv filter; is this needed?
				get_user_name(tid,name,32)
				formatex(line,250,"<tr%s><td height=1%>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>",i%2?"":" class='o'",timer[tid-1][TMR_DBUSER]>0?"y":"n",i,htmlspecialchars(name),getuserstatus(tid),parsetime(getusertime(tid)),timer[tid-1][TMR_CPSCNT],timer[tid-1][TMR_GCSCNT],parsetime(timer[tid-1][TMR_BSTTME]),timer[tid-1][TMR_BSTGCS],timer[tid-1][TMR_BSTCPS],timer[tid-1][TMR_MAPFIN])
				writen_len+=strlen(line)
				if(limit&&writen_len>(1003-strlen(cust_msg)))break
				fprintf(fh,line)
				//}
			}
			//1530-150-377=1003
			fprintf(fh,"<tr><td></td></tr><tr><td class=sb colspan=11></td></tr><tr><th colspan=4 height=1%>")
			fprintf(fh,"climb v%s by: Ian Cammarata</th><th colspan=7>%s</th></tr></table></body></html>",VERSION,cust_msg)
			fclose(fh)	
			score_ts=get_systime()
		}
		
		if(!limit)fpn=get_url(id,"climb_scores.html")
		show_motd(id,fpn,"Current Scores")
	}
	return PLUGIN_HANDLED
}

public parsetime(sec){//Convert seconds to time string with zero padded seconds field
	new timestr[9],mins
	mins=sec/60
	sec=sec%60
	formatex(timestr,8,"%d:%s%d",mins,sec<10?"0":"",sec)
	return timestr
}

public getusertime(id){//Calculate client climb time in seconds
	/*new ptime,cflags=timer[id-1][TMR_CFLAGS]
	ptime=timer[id-1][TMR_FINISH]-timer[id-1][TMR_STARTD]
	if(cflags&CF_STOP&&timer[id=1][TMR_SESFIN]>0)ptime=timer[id-1][TMR_FINISH]-timer[id-1][TMR_STARTD]
	else if(cflags&CF_STOP)ptime=0
	else if(cflags&CF_START)ptime=get_systime()-timer[id-1][TMR_STARTD]
	else if(cflags&CF_PAUSE)ptime=timer[id-1][TMR_STARTD]*/
	new cflags=timer[id-1][TMR_CFLAGS]
	if(cflags&CF_START)return get_systime()-timer[id-1][TMR_STARTD]
	else if(cflags&CF_PAUSE)return timer[id-1][TMR_STARTD]
	return timer[id-1][TMR_FINISH]-timer[id-1][TMR_STARTD]
}

public hudtime(){//Set clock on HUD to current climb time
	if(get_pcvar_num(p_climb)){
		new id,players[32],num,cltime
		get_players(players,num,"ach")
		for(new i=0;i<num;i++){
			id=players[i]
			cltime=getusertime(id)+1
			if(timer[id-1][TMR_CFLAGS]&CF_PAUSE)clmsg(id,"PAUSED - say '/unpause' to resume.")
			
			hudtime_msg(id,cltime)
			for(new i=1;i<=spec_ids[id-1][0];i++)
				hudtime_msg(spec_ids[id-1][i],cltime)
		}
	}
	return 1
}
public hudtime_msg(id,cltime){
	message_begin(1,get_user_msgid("RoundTime"),{0,0,0},id)
	write_short(cltime)
	message_end()
	return PLUGIN_HANDLED
}

public getuserstatus(id){//Return string describing user status
	new msg[12]
	if(timer[id-1][TMR_CFLAGS]&CF_STOP&&timer[id-1][TMR_SESFIN]>0)msg="Finished"
	/*else switch(timer[id-1][TMR_CFLAGS]){
		case TMR_CFLAGS_STOP:	msg="Not Started"
		case TMR_CFLAGS_STRT: msg="Climbing"
		case TMR_CFLAGS_PAUS: msg="Paused"
	}*/
	else if(timer[id-1][TMR_CFLAGS]&CF_STOP)msg="Not Started"
	else if(timer[id-1][TMR_CFLAGS]&CF_START)msg="Climbing"
	else if(timer[id-1][TMR_CFLAGS]&CF_PAUSE)msg="PAUSED"
	return msg
}
//Show Hud Message
public clmsg(id, msg[]){
	set_hudmessage(get_pcvar_num(p_msg_r),get_pcvar_num(p_msg_g),get_pcvar_num(p_msg_b),get_pcvar_float(p_msg_x),get_pcvar_float(p_msg_y),0,0.0,5.0,0.5,0.5,4)
	show_hudmessage(id,msg)
	for(new i=1;i<=spec_ids[id-1][0];i++)
		show_hudmessage(spec_ids[id-1][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-1][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-1][0]++
				spec_ids[id2-1][spec_ids[id2-1][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-1]=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-1]=true
	return PLUGIN_CONTINUE
}
//==============================================================================
//	Start: Functions for use in if statements
//==============================================================================
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-1][TMR_CFLAGS]
	if(cflags&CF_STOP&&(get_user_flags(id)&ADMIN_RESERVATION||timer[id-1][TMR_SESFIN]>0))
		return 1
	return 0
}

public isalive(id){//Use in IF statements to automatically print error if false
	if(is_user_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-1][TMR_CFLAGS]&CF_PAUSE))return 1
	clmsg(id,"You can't execute this command while paused.")
	return 0
}

public regwarn(id){
	if(timer[id-1][TMR_DBUSER]<1){
		saytext(id,id,"^x04You must register/login for your stats to save. Say /climhelp for more info.")
		return 1
	}
	return 0
}
//==============================================================================
//	End: Functions for use in if statements
//==============================================================================
//	Start: Auto Heal functions
//==============================================================================
public damage(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_user_alive(id)){
		new chp=get_user_health(id)
		if(chp!=hp){
			new wpn,atk=get_user_attacker(id,wpn)
		//Heal hp immediately
			if((atk==0||(id==atk&&wpn==0))&&!task_exists(50+id))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_user_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)){
		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
		}
	}
	return PLUGIN_CONTINUE
}

public bhop_set_fail(tskid){
	bhop_fail[tskid-TSK_BHOP-1]=true
	return PLUGIN_HANDLED
}

public bhop_clear_fail(tskid){
	new id=tskid-TSK_CLEAR_FAIL
	bhop_failid[id-1]=0
	bhop_fail[id-1]=false
	return PLUGIN_HANDLED
}

//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-1][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,get_user_msgid("Flashlight"),_,id)
		write_byte(1)
		write_byte(100)
		message_end()
		message_begin(MSG_ONE,get_user_msgid("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,get_user_msgid("Flashlight"),_,id)
		write_byte(0)
		write_byte(100)
		message_end()
		message_begin(MSG_ONE,get_user_msgid("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-1][TMR_CFLAGS]&CF_LIGHT_ON)){
		timer[id-1][TMR_CFLAGS]+=CF_LIGHT_ON
		set_task(0.1,"flight_msg",TSK_FLIGHT+id,_,_,"b")
	}
	else timer[id-1][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-1][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
	new Float:c2[3],player,players[32],num
	get_players(players,num,"ac")
	unsolid(id)
	for(new i=0;i<num;i++){
		player=players[i]
		if(id!=player){
			entity_get_vector(player,EV_VEC_origin,c2)
			if(vector_distance(orig,c2)<74)unsolid(player)
		}
	}
	entity_set_vector(id,EV_VEC_velocity,Float:{0.0, 0.0, 0.0})
	entity_set_vector(id,EV_VEC_origin,orig)
	delay_duck(id)
	return 1
}

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)
}

public cvar_enabled(id,p_cvar){//Used in IF statements to automatically print error if false
	if(get_pcvar_num(p_cvar)==0){
		clmsg(id,"This command is disabled.")
		client_print(id,print_chat,"This command has been disabled by the server administrator.")
		return 0
	}
	return 1
}
//==============================================================================
//	Start: Climb command handling functions
//==============================================================================
public give_scout(id){
	for(new i=0;i<10;i++)give_item(id,"weapon_scout")
	return PLUGIN_HANDLED
}

public give_usp(id){
	for(new i=0;i<10;i++)give_item(id,"weapon_usp")
	return PLUGIN_HANDLED
}

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")
	}
	return PLUGIN_HANDLED
}

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")
	}
	return PLUGIN_HANDLED
}

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_user_alive(id)){
			//cs_set_user_team(id,3)//fixes respawn bug?
			frespawn(id)
		}
		else if(get_pcvar_num(p_climb)&&(get_user_flags(id)&ADMIN_RESERVATION?1:(cvar_enabled(id,p_allow_spectators)))){
			if(timer[id-1][TMR_CFLAGS]&CF_START)change_status(id,CF_PAUSE)
			cs_set_user_team(id,3)
			//going_spec=id
			set_msg_block(get_user_msgid("DeathMsg"),1)
			user_kill(id,1)
			set_task(0.1,"sortcssb")//Update frags to reorder scoreboard
		}
		return PLUGIN_HANDLED
	}
	return PLUGIN_CONTINUE
}

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)
	//Stuck
	else if(equali(cmd,"stuck")||equali(cmd,"unstuck"))stuck(id)
	//Pause
	else if(equali(cmd,"pause")||equali(cmd,"unpause"))change_status(id,CF_PAUSE)
	//Stop
	else if(equali(cmd,"stop"))stop(id)
	//Time/Scores
	else if(equali(cmd,"scoreboard")||equali(cmd,"score")||equali(cmd,"scores"))climbscores(id)
	//Top Scores
	else if(equali(cmd,"top10")||equali(cmd,"top15")||equali(cmd,"top")||equali(cmd,"highscores")||equali(cmd,"best")||equali(cmd,"rank"))highscores(id)
	//Respawn
	else if(equali(cmd,"respawn")||equali(cmd,"spawn")||equali(cmd,"restart")||equali(cmd,"reset")||equali(cmd,"start"))frespawn(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)
	//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: Blocked/Forwarded default CS commands
//==============================================================================
public client_kill(id){//Block kill, forward to spectate command
	if(get_pcvar_num(p_climb)==1){
		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(){
	//update from old db version
	//alter table climb_scores add boosts int
	//alter table climb_scores add wpns int
	//alter table climb_scores add score int
	//update climb_scores set boosts=-1
	//update climb_scores set wpns=-1
	static host[32],user[32],pass[32],name[32],type[12]
	
	get_cvar_string("climb_db_host",host,31)
	get_cvar_string("climb_db_user",user,31)
	get_cvar_string("climb_db_pass",pass,31)
	get_cvar_string("climb_db_name",name,31)
	get_cvar_string("climb_db_type",type,11)
	get_cvar_string("climb_db_prefix",db_prefix,31)

	new autoinc[15]="autoincrement"
	if(equal(type,"mysql"))autoinc="auto_increment"

	//SQL_SetAffinity(type)
	db_tuple=SQL_MakeDbTuple(host,user,pass,name)
	
	new query[600]
	formatex(query,599,"\
			create table %splayers (\
				user_id integer primary key %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 %sscores (\
				score_id integer primary key %s,\
				score integer,\
				server_ip char(15),\
				user_id integer,\
				map_name varchar(32),\
				fin_time integer,\
				cps integer,\
				gcs integer,\
				fin_cnt integer,\
				boosts integer,\
				wpns integer,\
				server_time_stamp integer);\
	",db_prefix,autoinc)
	SQL_ThreadQuery(db_tuple,"db_generic_handler",query)
	formatex(query,599,"create unique index scores_usermap_idx on %sscores (user_id, map_name);",db_prefix)
	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 %ssessions (\
				steam_id char(25) primary key,\
				user_id integer unique);\
	",db_prefix)
	SQL_ThreadQuery(db_tuple,"db_generic_handler",query)
	return PLUGIN_HANDLED
}
public db_generic_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
   
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	return PLUGIN_HANDLED
}

public auto_login(ida[1])
	return client_cmd(ida[0],"login")
	
public login(id){
	if(get_pcvar_num(p_climb)){
		if(climb_save){
			if(timer[id-1][TMR_DBUSER]>0){//Already logged in
				client_print(id,print_console,"[Climb] Login Error: You are already logged in.")
				return PLUGIN_HANDLED
			}
			new query[99],data[28]
			data[0]=id
			if(read_argc()>1){//Client is logging in with a user/pass
				new user[21],pass[21]
				read_argv(1,user,20)
				read_argv(2,pass,20)
				rotwtf(pass,6)
				data[1]=1
				formatex(query,98,"select user_id from %splayers where user_id=^"%s^" and password=^"%s^";",db_prefix,user,pass)
				formatex(data[2],25,user)
				SQL_ThreadQuery(db_tuple,"login_handler",query,data,28)
			}
			else{//Client is logging in with SteamID
				new sid[26]
				get_user_authid(id,sid,25)
				data[1]=2
				formatex(query,98,"select user_id, password from %splayers where steam_id=^"%s^";",db_prefix,sid)
				formatex(data[2],25,sid)
				SQL_ThreadQuery(db_tuple,"login_handler",query,data,28)
			}
		}
		else client_print(id,print_console,"[Climb] Login Error: Can't Login; Stats not enabled.")
	}
	return PLUGIN_HANDLED
}

public login_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	new id=data[0]
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
  
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	if(SQL_NumResults(query)<1)
		return client_print(id,print_console,"[Climb] Login Error: Not a valid account.")
	
	if(data[1]==2){
		new pass[7]
		SQL_ReadResult(query,1,pass,6)
		if(equali(pass,"shared")){
			client_print(id,print_console,"[Climb] Login Error: You are using a shared SteamID.  Please login with a username and password.")
			return PLUGIN_HANDLED
		}
	}
	timer[id-1][TMR_DBUSER]=SQL_ReadResult(query,0)
	new msg[100]
	formatex(msg,99,"[Climb] Login: Success - Account: %s",data[2])
	client_print(id,print_console,msg)
	format(msg,99,"^x04%s",msg)
	saytext(id,id,msg)
	db_load(id)
	return PLUGIN_HANDLED
}

public logout(id){
	for(new i=0;i<8;i++)origins[id-1][i]=0.0
	for(new i=0;i<11;i++)timer[id-1][i]=0
}

public db_load(id){
	new query[150],mapname[33],data[1]
	data[0]=id
	get_mapname(mapname,32)
	formatex(query,149,"select fin_time, cps, gcs, fin_cnt from %sscores where user_id=%d and map_name=^"%s^";",db_prefix,timer[id-1][TMR_DBUSER],mapname)
	SQL_ThreadQuery(db_tuple,"db_load_handler",query,data,1)
	return PLUGIN_HANDLED
}

public db_load_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
  
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	new msg[100],id=data[0]
	if(!SQL_NumResults(query))
		msg="^x04No stats available for this account on the current map."
	else{
		new mapname[33],timestr[9]
		get_mapname(mapname,32)
		timer[id-1][TMR_BSTTME]=SQL_ReadResult(query,0)
		timer[id-1][TMR_BSTCPS]=SQL_ReadResult(query,1)
		timer[id-1][TMR_BSTGCS]=SQL_ReadResult(query,2)
		timer[id-1][TMR_MAPFIN]=SQL_ReadResult(query,3)
		timestr=parsetime(timer[id-1][TMR_BSTTME])
		formatex(msg,99,"^x04Stats loaded for %s - %s^t(%d CPS/ %d GCS)^tCompleted %d",mapname,timestr,timer[id-1][TMR_BSTCPS],timer[id-1][TMR_BSTGCS],timer[id-1][TMR_MAPFIN])
	}
	saytext(id,id,msg)
	return PLUGIN_HANDLED
}

public db_save(id){
	new query[150],name[33]
	get_mapname(name,32)
	if(timer[id-1][TMR_MAPFIN]==1)
		formatex(query,149,"insert into %sscores (user_id, map_name, fin_time, cps, gcs, fin_cnt)\
		 																	values (%d, ^"%s^", %d, %d, %d, %d);\
		",db_prefix,timer[id-1][TMR_DBUSER],name,timer[id-1][TMR_BSTTME],timer[id-1][TMR_BSTCPS],timer[id-1][TMR_BSTGCS],timer[id-1][TMR_MAPFIN])
	else
		formatex(query,149,"update %sscores set fin_time=%d, cps=%d, gcs=%d, fin_cnt=%d where user_id=%d and map_name=^"%s^";",db_prefix,timer[id-1][TMR_BSTTME],timer[id-1][TMR_BSTCPS],timer[id-1][TMR_BSTGCS],timer[id-1][TMR_MAPFIN],timer[id-1][TMR_DBUSER],name)
	SQL_ThreadQuery(db_tuple,"db_save_handler",query)
	get_user_name(id,name,32)
	formatex(query,149,"update %splayers set alias=^"%s^" where user_id=%d;",db_prefix,name,timer[id-1][TMR_DBUSER])
	SQL_ThreadQuery(db_tuple,"db_save_handler",query)
	return PLUGIN_HANDLED
}

public db_save_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
  
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	return PLUGIN_HANDLED
}

public reg(id){
	if(get_pcvar_num(p_climb)){
		if(climb_save){
			new query[100],sid[26],name[33],data[43]
			data[0]=id
			data[1]=0
			get_user_authid(id,sid,25)
			get_user_name(id,name,32)
			//Register user/pass
			if(read_argc()>1){
				if(read_argc()!=3){
					client_print(id,print_console,"[Climb] Registration Error: Invalid number of arguments")
					return PLUGIN_HANDLED
				}
				new user[21],pass[50]
				//Read password and check length
				read_argv(2,pass,20)
				if(strlen(pass)<10){
					client_print(id,print_console,"[Climb] Registration Error: Password must be at least 10 characters.")
					return PLUGIN_HANDLED
				}
				//Read user
				read_argv(1,user,20)
				//Store user/pass in data array to pass to handler for autologin
				formatex(data[2],20,user)
				formatex(data[22],20,pass)
				//Create password hash
				rotwtf(pass,6)
				//Register shared SteamID
				formatex(query,99,"insert into %splayers (steam_id,password) values (^"%s^",'shared')",db_prefix,sid)
				SQL_ThreadQuery(db_tuple,"reg_handler",query,data,43)
				//Register user/pass 
				data[1]=1
				formatex(query,99,"insert into %splayers (user_name,password,alias) values (^"%s^",^"%s^",^"%s^")",db_prefix,user,pass,name)
				SQL_ThreadQuery(db_tuple,"reg_handler",query,data,43)
			}
			//Else register SteamID
			else{
				data[1]=2
				formatex(query,99,"insert into %splayers (steam_id,alias) values (^"%s^",^"%s^")",db_prefix,sid,name)
				SQL_ThreadQuery(db_tuple,"reg_handler",query,data,43)
			}
		}
		else client_print(id,print_console,"[Climb] Registration Error: Can't Register; Stats not enabled.")
	}
	return PLUGIN_HANDLED
}

public reg_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	new id=data[0],flag=data[1],user[21],pass[21]
	format(user,20,data[2])
	format(pass,20,data[22])
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("[Climb] Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("[Climb] Query failed: %s",error)
   
	if(errnum){
		client_print(id,print_console,"[Climb] Registration Error: Database error, please notify the server admin.")
		return server_print("[Climb] Query Error: %s",error)
	}
	
	
	if(flag==0)client_print(id,print_console,"[Climb] Recorded shared SteamID.")
	else{
		client_print(id,print_console,"[Climb] Registration Successful.")
		new cmd[50]
		formatex(cmd,49,"login %s %s",user,pass)
		client_cmd(id,cmd)
	}
	return PLUGIN_HANDLED
}

public highscores(id){//Show High Scores
	new hsurl[150],mapname[33]
	get_pcvar_string(p_stats_hsurl,hsurl,149)
	get_mapname(mapname,32)
	if(strlen(hsurl)){
		formatex(hsurl,149,hsurl,mapname)
		show_motd(id,hsurl,"High Scores")
		return PLUGIN_HANDLED
	}
	if(get_systime()-hscore_ts>10){
		new query[250],data[1]
		data[0]=id
		formatex(query,249,"select p.alias, s.fin_time, s.cps, s.gcs, s.fin_cnt from %sscores s, %splayers p where s.map_name=^"%s^" and p.user_id=s.user_id order by s.fin_time, s.gcs, s.cps limit 20;",db_prefix,db_prefix,mapname)
		SQL_ThreadQuery(db_tuple,"hs_handler",query,data,1)
		hscore_ts=get_systime()
	}
	else{
		new fpn[100],bool:limit=true
		limit=bool:get_limit()
		fpn=pathname("climb_highscores.html")
		if(!limit)fpn=get_url(id,"climb_highscores.html")
		show_motd(id,fpn,"High Scores")
	}
	
	return PLUGIN_HANDLED
}

public hs_handler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime){
	if(failstate == TQUERY_CONNECT_FAILED)
		return server_print("Climb: Couldn't connect to database.")
	else if(failstate == TQUERY_QUERY_FAILED)
		return server_print("Climb: Query failed: %s",error)
   
	if(errnum)
		return server_print("Climb: Query Error: %s",error)
	
	new id=data[0],msg[50],num=SQL_NumResults(query)
	if(!num){
		msg="^x04[Climb] No stats available for the current map."
		return saytext(id,id,msg)
	}
	
	new fpn[100],bool:limit=true,cust_msg[129]
	get_pcvar_string(p_stats_msg,cust_msg,128)
	if(!get_limit())limit=false
	fpn=pathname("climb_highscores.html")
	new fh=fopen(fpn,"w")
	fprintf(fh,"<html><head>%s<link rel=stylesheet href=http://ian.cammarata.us/climb/sb.css></head><body><table><tr>",limit?"":"<meta http-equiv=pragma content=no-cache>")
	fprintf(fh,"<th height=1%%>#</th><th width=45%%>Name</th><th width=20%%>Time</th><th width=10%%>CP#</th>")
	fprintf(fh,"<th width=10%%>GC#</th><th width=10%%>Completed</th></tr><tr><td class=st colspan=6></td></tr>")
	//99+88+92=279
	new name[33],line[151],writen_len
	for(new i=1;i<=num;i++){
		SQL_ReadResult(query,0,name,32)
		formatex(line,150,"<tr%s><td height=1%>%d</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>",i%2?"":" class='o'",i,htmlspecialchars(name),parsetime(SQL_ReadResult(query,1)),SQL_ReadResult(query,2),SQL_ReadResult(query,3),SQL_ReadResult(query,4))
		writen_len+=strlen(line)
		if(limit&&writen_len>(1101-strlen(cust_msg)))break
		fprintf(fh,line)
		SQL_NextRow(query)
	}
	//1530-150-279=1101
	fprintf(fh,"<tr><td></td></tr><tr><td class=sb colspan=6></td></tr><tr><th colspan=2 height=1%>")
	fprintf(fh,"climb v %s by: Ian Cammarata</th><th colspan=4>%s</th></tr></table></body></html>",VERSION,cust_msg)
	fclose(fh)
	
	if(!limit)fpn=get_url(id,"climb_highscores.html")
	show_motd(id,fpn,"High Scores")
	
	return PLUGIN_HANDLED
}
//==============================================================================
//	End: Database functions
//==============================================================================
public rotwtf(string[],out_len){
	new len=strlen(string),str[99],cnt=0,tok=len-1
	copy(str,out_len,string)
	for(new index=0;index<11;index++){
		if('a'<=string[index]<='z')
			str[index]=(str[index]-'a'+string[tok]+index)
		else if('A'<=str[index]<='Z')
			str[index]=(str[index]-'A'+string[tok]+index)
		else if('0'<=str[index]<='9')
			str[index]=(str[index]-'0'+string[tok]+index)
		switch(cnt){
			case 0:str[index]=str[index]%26+'a'
			case 1:str[index]=str[index]%26+'A'
			case 2:str[index]=str[index]%10+'0'
		}
		tok--
		if(tok<0)tok=len-1
		cnt++
		if(cnt==3)cnt=0
	}
	copy(string,out_len,str)
	return PLUGIN_HANDLED
}

public plugin_end(){
	if(climb_save)SQL_FreeHandle(db_tuple)
	set_cvar_num("climb",0)
	return PLUGIN_CONTINUE
}

Contact
ViewVC Help
Powered by ViewVC 1.0.4