//Batgirl - Based off of Spiderman

/* CVARS - copy and paste to shconfig.cfg

//Batgirl
batgirl_level 9
batgirl_moveacc 650		//How quickly she can move while on the zipline
batgirl_reelspeed 1000		//How fast hook line reels in
batgirl_hookstyle 3		//1=spacedude, 2=spacedude auto reel (spiderman), 3=cheap kids real	(batgirl)
batgirl_hooksky 0		//0=no sky hooking 1=sky hooking allowed
batgirl_teamcolored 1		//1=teamcolored zip lines 0=white zip lines
batgirl_maxhooks -1		//Max ammout of hooks allowed (-1 is an unlimited ammount)

*/

#include <amxmodx>
#include <fakemeta>
#include <superheromod>

// GLOBAL VARIABLES
#define HOOKBEAMLIFE  100
#define HOOK_DELTA_T  0.1  // units per second

new gHeroName[]="Batgirl"
new bool:gHasBatgirl[SH_MAXSLOTS+1]
new gHookLocation[SH_MAXSLOTS+1][3]
new gHookLength[SH_MAXSLOTS+1]
new bool:gHooked[SH_MAXSLOTS+1]
new Float:gHookCreated[SH_MAXSLOTS+1]
new gHooksLeft[SH_MAXSLOTS+1]
new gSpriteHookLine
new const gSoundHook[] = "weapons/xbow_hit2.wav"
new gPcvarMoveAcc, gPcvarReelSpeed, gPcvarHookStyle, gPcvarHookSky
new gPcvarTeamColored, gPcvarMaxHooks, gPcvarSvGravity
//----------------------------------------------------------------------------------------------
public plugin_init()
{
	// Plugin Info
	register_plugin("SUPERHERO Batgirl", "1.2", "sharky/JTP10181")

	// DO NOT EDIT THIS FILE TO CHANGE CVARS, USE THE SHCONFIG.CFG
	register_cvar("batgirl_level", "9")
	gPcvarMoveAcc = register_cvar("batgirl_moveacc", "650")
	gPcvarReelSpeed = register_cvar("batgirl_reelspeed", "1000")
	gPcvarHookStyle = register_cvar("batgirl_hookstyle", "3")
	gPcvarHookSky = register_cvar("batgirl_hooksky", "0")
	gPcvarTeamColored = register_cvar("batgirl_teamcolored", "1")
	gPcvarMaxHooks = register_cvar("batgirl_maxhooks", "-1")

	// FIRE THE EVENT TO CREATE THIS SUPERHERO!
	shCreateHero(gHeroName, "Bat-Grappling Hook", "Grappling Hook - You now have the Bat-Grapple Hook. Shoot your Hook and automatically be ziplined to the target", true, "batgirl_level")

	// REGISTER EVENTS THIS HERO WILL RESPOND TO! (AND SERVER COMMANDS)
	// INIT
	register_srvcmd("batgirl_init", "batgirl_init")
	shRegHeroInit(gHeroName, "batgirl_init")

	// KEY UP
	register_srvcmd("batgirl_ku", "batgirl_ku")
	shRegKeyUp(gHeroName, "batgirl_ku")

	// KEY DOWN
	register_srvcmd("batgirl_kd", "batgirl_kd")
	shRegKeyDown(gHeroName, "batgirl_kd")

	// DEATH
	register_event("DeathMsg", "batgirl_death", "a")  // Re-uses KeyUp!

	// Reset the HookCounts every round (regardless of batgirlpower)
	register_event("ResetHUD", "new_spawn", "b")

	gPcvarSvGravity = get_cvar_pointer("sv_gravity")
}
//----------------------------------------------------------------------------------------------
public plugin_precache()
{
	precache_sound(gSoundHook)
	gSpriteHookLine = precache_model("sprites/zbeam4.spr")
}
//----------------------------------------------------------------------------------------------
public batgirl_init()
{
	new temp[6]
	// First Argument is an id
	read_argv(1, temp, 5)
	new id = str_to_num(temp)

	// 2nd Argument is 0 or 1 depending on whether the id has the hero
	read_argv(2, temp, 5)
	new hasPowers = str_to_num(temp)

	gHasBatgirl[id] = (hasPowers != 0)
	if ( gHooked[id] ) batgirl_hook_off(id)
}
//----------------------------------------------------------------------------------------------
public new_spawn(id)
{
	gHooksLeft[id] = get_pcvar_num(gPcvarMaxHooks)

	if ( gHooked[id] ) batgirl_hook_off(id)
}
//----------------------------------------------------------------------------------------------
public batgirl_kd()
{
	new temp[6]
	read_argv(1, temp, 5)
	new id = str_to_num(temp)

	if ( gHooked[id] || !is_user_alive(id) || !gHasBatgirl[id] || !hasRoundStarted() ) return

	if ( pass_aim_test(id) )
	{
		new hooksleft = gHooksLeft[id]

		if ( hooksleft == 0 ) {
			playSoundDenySelect(id)
			return
		}

		if ( hooksleft > 0 ) gHooksLeft[id] = --hooksleft

		if ( -1 < hooksleft < 6 ) {
			client_print(id, print_center, "You have %d bat-grapple hook%s left", hooksleft, hooksleft == 1 ? "" : "s")
		}

		gHooked[id] = true

		set_user_info(id, "ROPE", "1")

		new parm[2], user_origin[3]
		parm[0] = id

		get_user_origin(id, user_origin)
		get_user_origin(id, gHookLocation[id], 3)

		gHookLength[id] = get_distance(gHookLocation[id], user_origin)

		set_user_gravity(id, 0.001)

		beamentpoint(id)

		emit_sound(id, CHAN_STATIC, gSoundHook, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)

		parm[1] = get_pcvar_num(gPcvarHookStyle)

		set_task(HOOK_DELTA_T, "batgirl_check_web", id, parm, 2, "b")
	}
}
//----------------------------------------------------------------------------------------------
bool:pass_aim_test(id)
{
	// Can hook sky ignore test
	if ( get_pcvar_num(gPcvarHookSky) ) return true

	new origin[3], Float:Orig[3]
	get_user_origin(id, origin, 3)
	IVecFVec(origin, Orig)

	if ( engfunc(EngFunc_PointContents, Orig) == CONTENTS_SKY )
	{
		client_print(id, print_chat, "[SH](%s) You cannot hook to the sky", gHeroName)
		return false
	}

	return true
}
//----------------------------------------------------------------------------------------------
public batgirl_ku()
{
	new temp[6]
	read_argv(1, temp, 5)
	new id = str_to_num(temp)

	if ( gHooked[id] ) batgirl_hook_off(id)
}
//----------------------------------------------------------------------------------------------
public batgirl_check_web(parm[])
{
	new id = parm[0]

	// hookstyle = parm[1]
	switch(parm[1])
	{
		case 1: batgirl_physics(id, false)
		case 2: batgirl_physics(id, true)
		default: batgirl_cheap_reel(id)
	}
}
//----------------------------------------------------------------------------------------------
batgirl_physics(id, bool:autoReel)
{
	if ( !gHooked[id]  ) return

	if ( !is_user_alive(id) ) {
		batgirl_hook_off(id)
		return
	}

	// Refresh the beam effect
	if ( gHookCreated[id] + HOOKBEAMLIFE/10 <= get_gametime() ) {
		beamentpoint(id)
	}

	new user_origin[3], null[3], A[3], D[3], buttonadjust[3], buttonpress
	new Float:vTowards_A, Float:DvTowards_A, Float:velocity[3]

	get_user_origin(id, user_origin)

	pev(id, pev_velocity, velocity)

	buttonpress = pev(id, pev_button)

	if ( buttonpress & IN_FORWARD ) ++buttonadjust[0]
	if ( buttonpress & IN_BACK ) --buttonadjust[0]

	if ( buttonpress & IN_MOVERIGHT ) ++buttonadjust[1]
	if ( buttonpress & IN_MOVELEFT ) --buttonadjust[1]

	if ( buttonpress & IN_JUMP ) ++buttonadjust[2]
	if ( buttonpress & IN_DUCK ) --buttonadjust[2]

	if ( buttonadjust[0] || buttonadjust[1] ) {
		new user_look[3], move_direction[3]
		get_user_origin(id, user_look, 2)
		user_look[0] -= user_origin[0]
		user_look[1] -= user_origin[1]

		move_direction[0] = buttonadjust[0]*user_look[0] + user_look[1]*buttonadjust[1]
		move_direction[1] = buttonadjust[0]*user_look[1] - user_look[0]*buttonadjust[1]
		move_direction[2] = 0

		new move_dist = get_distance(null, move_direction)
		new Float:accel = get_pcvar_float(gPcvarMoveAcc) * HOOK_DELTA_T

		velocity[0] += move_direction[0] * accel / move_dist
		velocity[1] += move_direction[1] * accel / move_dist
	}

	if ( buttonadjust[2] < 0 || (buttonadjust[2] && gHookLength[id] >= 60) ) {
		gHookLength[id] -= floatround(buttonadjust[2] * get_pcvar_float(gPcvarReelSpeed) * HOOK_DELTA_T)
	}
	else if ( autoReel && !(buttonpress&IN_DUCK) && gHookLength[id] >= 200 ) {
		++buttonadjust[2]
		gHookLength[id] -= floatround(buttonadjust[2] * get_pcvar_float(gPcvarReelSpeed) * HOOK_DELTA_T)
	}

	A[0] = gHookLocation[id][0] - user_origin[0]
	A[1] = gHookLocation[id][1] - user_origin[1]
	A[2] = gHookLocation[id][2] - user_origin[2]

	new distA = get_distance(null, A)
	distA = (distA ? distA : 1)	// Avoid dividing by 0

	vTowards_A = (velocity[0] * A[0] + velocity[1] * A[1] + velocity[2] * A[2]) / distA
	DvTowards_A = float((get_distance(user_origin, gHookLocation[id]) - gHookLength[id]) * 4)

	D[0] = A[0]*A[2] / distA
	D[1] = A[1]*A[2] / distA
	D[2] = -(A[1]*A[1] + A[0]*A[0]) / distA

	new distD = get_distance(null, D)
	if ( distD > 10 ) {
		new Float:acceleration = ((-get_pcvar_num(gPcvarSvGravity)) * D[2] / distD) * HOOK_DELTA_T
		velocity[0] += (acceleration * D[0]) / distD
		velocity[1] += (acceleration * D[1]) / distD
		velocity[2] += (acceleration * D[2]) / distD
	}

	new Float:difference = DvTowards_A - vTowards_A

	velocity[0] += (difference * A[0]) / distA
	velocity[1] += (difference * A[1]) / distA
	velocity[2] += (difference * A[2]) / distA

	set_pev(id, pev_velocity, velocity)
}
//----------------------------------------------------------------------------------------------
batgirl_cheap_reel(id)
{
	// Cheat Web - just drags you where you shoot it...
	if ( !gHooked[id] ) return

	if ( !is_user_alive(id) )
	{
		batgirl_hook_off(id)
		return
	}

	new user_origin[3]
	new Float:velocity[3]

	get_user_origin(id, user_origin)

	new distance = get_distance(gHookLocation[id], user_origin)

	if ( distance > 60 )
	{
		new Float:inverseTime = get_pcvar_float(gPcvarReelSpeed) / distance
		velocity[0] = (gHookLocation[id][0] - user_origin[0]) * inverseTime
		velocity[1] = (gHookLocation[id][1] - user_origin[1]) * inverseTime
		velocity[2] = (gHookLocation[id][2] - user_origin[2]) * inverseTime
	}

	set_pev(id, pev_velocity, velocity)
}
//----------------------------------------------------------------------------------------------
batgirl_hook_off(id)
{
	gHooked[id] = false

	set_user_info(id, "ROPE", "0")

	killbeam(id)

	if ( is_user_connected(id) ) shSetGravityPower(id)

	remove_task(id)
}
//----------------------------------------------------------------------------------------------
beamentpoint(id)
{
	if ( !is_user_connected(id) ) return

	new rgb[3] = {250, 250, 250}

	if ( get_pcvar_num(gPcvarTeamColored) )
	{
		switch(cs_get_user_team(id))
		{
			case CS_TEAM_T: rgb = {255, 0, 0}
			case CS_TEAM_CT: rgb = {0, 0, 255}
		}
	}

	message_begin(MSG_BROADCAST, SVC_TEMPENTITY)
	write_byte(TE_BEAMENTPOINT)
	write_short(id)
	write_coord(gHookLocation[id][0])
	write_coord(gHookLocation[id][1])
	write_coord(gHookLocation[id][2])
	write_short(gSpriteHookLine)	// sprite index
	write_byte(0)		// start frame
	write_byte(0)		// framerate
	write_byte(HOOKBEAMLIFE)// life
	write_byte(10)		// width
	write_byte(0)		// noise
	write_byte(rgb[0])       // r, g, b
	write_byte(rgb[1])       // r, g, b
	write_byte(rgb[2])       // r, g, b
	write_byte(150)        // brightness
	write_byte(0)          // speed
	message_end()

	gHookCreated[id] = get_gametime()
}
//----------------------------------------------------------------------------------------------
killbeam(id)
{
	message_begin(MSG_BROADCAST, SVC_TEMPENTITY)
	write_byte(TE_KILLBEAM)
	write_short(id)
	message_end()
}
//----------------------------------------------------------------------------------------------
public batgirl_death()
{
	new id = read_data(2)

	if ( id <= 0 || id > SH_MAXSLOTS ) return

	if ( gHooked[id] ) batgirl_hook_off(id)
}
//----------------------------------------------------------------------------------------------
public client_disconnect(id)
{
	// stupid check but lets see
	if ( id <= 0 || id > SH_MAXSLOTS ) return

	// Yeah don't want any left over residuals
	remove_task(id)
}
//----------------------------------------------------------------------------------------------