/*
	--- Intro ---
	   This is Crossbow from HL.It costs 25 Ammo packs(default).To attack press attack1
	button, to use zoom press attack2.If you shoot while zooming crossbow just hit enemy/
	wall, but if you are shooting without zoom bolt will explode and do more damage.
	You have 5 bolts in a clip and 25 in a back pack.
	
	--- Credits ---
	NiHiLaNTh - Plugin
	VEN - Finding weapon entities
	Arkshine - Ham_Item_Deploy and getting fov.
	meTaLiCroSS - Weapon touch
	
	--- Changelog ---
	v1.0 - First release
	v1.1 - Fixed : ammo was not updated correctly, optimized bolt flight, crap runtime 
	 error
	v1.2 - Fixed some little bugs
	v1.3 - Fixed bug with drop
	
	--- Good Luck & Have Fun ;) ---
*/	

#include <amxmodx>
#include <cstrike>
#include <engine>
#include <fakemeta>
#include <fun>
#include <hamsandwich>
#include <zombieplague>

// Plugin info
new const PLUGIN[] = "[ZP] Extra Item: Crossbow"
new const VERSION[] = "1.3"
new const AUTHOR[] = "NiHiLaNTh"

// Crossbow/Bolt models
new const P_MODEL[] = "models/p_crossbow.mdl"
new const V_MODEL[] = "models/v_crossbow.mdl"
new const W_MODEL[] = "models/w_crossbow.mdl"
new const BOLT_MODEL[] = "models/crossbow_bolt.mdl"

// Some sounds
new const XBOW_SHOOT[] = "weapons/xbow_fire1.wav"
new const XBOW_HITWALL[] = "weapons/xbow_hit1.wav"
new const XBOW_HITSTAB[] = "weapons/xbow_hitbod1.wav"
new const XBOW_RELOAD[] = "weapons/xbow_reload1.wav"

// Sprite
new const EXPLO_SPRITE[] = "sprites/zerogxplode.spr"

// Cached sprite indexes
new sExplo

// CVAR pointers
new pDamage, pDamage2, pRadius

// Item ID
new g_crossbow

// Player variables
new g_hasXbow[33] // whether playerhas Crossbow
new Float:g_last_shot_time[33] // Last time Crossbow used

// Max players
new g_maxplayers

// Global vars
new g_restarted

// Message ID's
new g_msgCurWeapon, g_msgAmmoX

// Weapon animation sequences
enum
{
	crossbow_idle1,
	crossbow_idle2,
	crossbow_fidget1,
	crossbow_fidget2,
	crossbow_fire1,
	crossbow_fire2,
	crossbow_fire3,
	crossbow_reload,
	crossbow_draw1,
	crossbow_draw2,
	crossbow_holster1,
	crossbow_holster2
}

// Item cost
#define COST 25

const USE_USING = 2

// Precache
public plugin_precache()
{
	// Models
	precache_model(P_MODEL)
	precache_model(V_MODEL)
	precache_model(W_MODEL)
	precache_model(BOLT_MODEL)

	// Sounds
	precache_sound(XBOW_SHOOT)
	precache_sound(XBOW_HITWALL)
	precache_sound(XBOW_HITSTAB)
	precache_sound(XBOW_RELOAD)
	precache_sound("weapons/357_cock1.wav")
	
	// Sprite
	sExplo = precache_model(EXPLO_SPRITE)
}

// Init
public plugin_init()
{
	// New plugin
	register_plugin(PLUGIN, VERSION, AUTHOR)
	register_cvar("zp_xbow_version", VERSION, FCVAR_SERVER)
	
	// New extra item
	g_crossbow = zp_register_extra_item("Crossbow", COST, ZP_TEAM_HUMAN)
	
	// Events
	register_event("HLTV", "Event_NewRound", "a", "1=0", "2=0");
	register_event("TextMsg", "Event_GameRestart", "a", "2=#Game_Commencing", "2=#Game_will_restart_in");
	
	// Forwards
	register_forward(FM_CmdStart, "fw_CmdStart")
	register_forward(FM_SetModel, "fw_SetModel")
	RegisterHam(Ham_Item_Deploy, "weapon_awp", "fw_CrossbowDeploy", 1)
	register_forward(FM_UpdateClientData, "fw_UpdateClientData_Post", 1)
	register_forward(FM_Touch, "fw_Touch")
	RegisterHam(Ham_Killed, "player", "fw_PlayerKilled_Post", 1)
	RegisterHam(Ham_Weapon_Reload, "weapon_awp", "fw_WeaponReload", 1)
	RegisterHam(Ham_Use, "func_tank", "fw_UseStationary")
	RegisterHam(Ham_Use, "func_tankmortar", "fw_UseStationary")
	RegisterHam(Ham_Use, "func_tankrocket", "fw_UseStationary")
	RegisterHam(Ham_Use, "func_tanklaser", "fw_UseStationary")
	
	// Touches
	register_touch("xbow_bolt", "*", "bolt_touch")
	register_touch("xbow_bolt_xplode", "*", "bolt_touch2")
	
	// CVARs
	pDamage = register_cvar("zp_crossbow_damage", "175") // Damage done by expl
	pDamage2 = register_cvar("zp_crossbow_damage2", "300") // Damage done by bolt
	pRadius = register_cvar("zp_crossbow_radius", "75")
	
	// Store maxplayers in a global variable
	g_maxplayers = get_maxplayers()

	// Messages
	g_msgCurWeapon = get_user_msgid("CurWeapon")
	g_msgAmmoX = get_user_msgid("AmmoX")
}

// When client connected, he can have our weapon
public client_connect(id)
{
	g_hasXbow[id] = false
}

// Someone wants to buy our extra item
public zp_extra_item_selected(id, itemid)
{
	if (itemid == g_crossbow)
	{
		// Already have it
		if (g_hasXbow[id] || user_has_weapon(id, CSW_AWP))
		{
			// Warn player
			client_print(id, print_center, "Already have Crossbow!")
			
			// Return ammo packs back
			zp_set_user_ammo_packs(id, zp_get_user_ammo_packs(id) + COST)
		}
		// Otherwise you can buy it
		else
		{
			// Notice
			client_print(id, print_chat, "[ZP] You bought crossbow")
	
			// Give him awp
			give_item(id, "weapon_awp")
			
			new weapon_id = find_ent_by_owner(-1, "weapon_awp", id)
			
			if (weapon_id)
			{
				cs_set_weapon_ammo(weapon_id, 10)
			}
			
			cs_set_user_bpammo(id, CSW_AWP, 50)
			
			//client_print(id, print_chat, "BP AMMO: %d", cs_get_user_bpammo(id, CSW_AWP))
			
			// Change models
			ChangeModels(id)
			
			// Reset
			g_hasXbow[id] = true
		
			// Update HUD
			UpdateHud(id)
		}
	}
}

// Target who have our weapon was infected - reset vars
public zp_user_infected_post(id, infector, nemesis)
{
	if (g_hasXbow[id])
	{
		g_hasXbow[id] = false
	}
}

// New round started
public Event_NewRound(id)
{
	if (g_restarted)
	{
		// Strip from Crossnow if game have been restarted
		for (new i = 1; i <= get_maxplayers(); i++)
		{
			g_hasXbow[i] = false
		}
		g_restarted = false
	}
}

// Restart
public Event_GameRestart()
{
	g_restarted = true
}

// Called when player start a command(shoot, jump etc.)
public fw_CmdStart(id, uc_handle, seed)
{
	// Ignore dead zombies/nemesis/those who haven't our weapons
	if (!is_user_alive(id) || zp_get_user_zombie(id) || zp_get_user_nemesis(id) || !g_hasXbow[id])
		return FMRES_IGNORED

	// Get amo, clip, and weapon	
	new ammo, clip, weapon = get_user_weapon(id, clip, ammo)
	
	// Not AWP
	if (weapon != CSW_AWP)
		return FMRES_IGNORED
		
	// Buttons	
	new buttons = get_uc(uc_handle, UC_Buttons)
	
	new ent = find_ent_by_owner(-1, "weapon_awp", id)
	
	// Attack1 button was pressed
	if (buttons & IN_ATTACK)
	{
		// We have enough ammo
		if (ammo >= 1)
		{
			// Next shot time has come
			if (get_gametime() - g_last_shot_time[id] > 2.0)
			{
				// Get FOV(Arkshine)
				new fov; fov = pev(id, pev_fov)
				
				// Default FOV
				if (fov == 90)
				{
					FireCrossbow(id, 0)
				}
				// Is zooming?
				else
					FireCrossbow(id, 1)
				
				// Decrease ammo
				cs_set_weapon_ammo(ent, cs_get_weapon_ammo(get_pdata_cbase(id, 373)) - 1)
					
				// Update HUD
				UpdateHud(id)
				
				// Remember last shot time
				g_last_shot_time[id] = get_gametime()
			}
		}
		else
		{
			// Play empty sound
			emit_sound(id, CHAN_WEAPON, "weapons/357_cock1.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
		}
		
		// Remove attack button from their buttons mask
		buttons &= ~IN_ATTACK
		set_uc(uc_handle, UC_Buttons, buttons)
	}
	
	// Attack2 button was pressed
	if (buttons & IN_ATTACK2)
	{
		// Update HUD(bugfix)
		UpdateHud(id)
	}
	
	return FMRES_HANDLED
}

// Usually called after player dropped weapons
public fw_SetModel(ent, const model[])
{
	// Prevent invalid entity messages
	if (!pev_valid(ent))	
		return FMRES_IGNORED
		
	// Not required model
	if(!equali(model, "models/w_awp.mdl")) 
		return FMRES_IGNORED
	
	// Get owner and classname
	new owner = pev(ent, pev_owner)
	new classname[33]; pev(ent, pev_classname, classname, charsmax(classname))
	
	// Entity classname is a weaponbox
	if(equal(classname, "weaponbox"))
	{
		// The weapon owner has a crossbow
		if(g_hasXbow[owner])
		{
			// Strip from crossbow
			g_hasXbow[owner] = false
			
			// Set world model
			engfunc(EngFunc_SetModel, ent, W_MODEL)
			
			// Update HUD
			UpdateHud(owner)
			
			// Touch fix
			set_task(0.1, "touch_fix", owner)
			
			return FMRES_SUPERCEDE
		}
	}
	
	return FMRES_IGNORED
	
}

// Called when player is holding this weapon
public fw_CrossbowDeploy(iEnt)
{
	// Get ID
	new id = get_pdata_cbase(iEnt, 41, 5)
	
	if (g_hasXbow[id])
	{
		// Change models
		ChangeModels(id)
		
		// Play animation
		UTIL_PlayWeaponAnimation(id, crossbow_draw1)
		
		// Update HUD
		UpdateHud(id)
	}
}

// Update client data
public fw_UpdateClientData_Post(id, sw, cd_handle)
{
	// Ignore dead zombies/nemesis/those who haven't our weapons
	if (!is_user_alive(id) || zp_get_user_zombie(id) || zp_get_user_nemesis(id) || !g_hasXbow[id])
		return FMRES_IGNORED
	
	// Get ammo, clip and weapon
	new ammo, clip, weapon = get_user_weapon(id, clip, ammo)
	
	// Not AWP
	if (weapon != CSW_AWP)
		return FMRES_IGNORED
	
	// Block default sounds
	set_cd(cd_handle, CD_ID, 0)
	return FMRES_HANDLED
}

// Called when player touch something
public fw_Touch(ptr, ptd)
{
	// Invalid ent/toucher
	if(!pev_valid(ptr) || !pev_valid(ptr))
		return FMRES_IGNORED;
	
	// Get model, toucherclass, entityclass
	new model[33], toucherclass[33], entityclass[33]
	pev(ptr, pev_model, model, charsmax(model))
	pev(ptr, pev_classname, entityclass, charsmax(entityclass))
	pev(ptd, pev_classname, toucherclass, charsmax(toucherclass))
	
	// TOucher isn't player and entity isn't weapon
	if (!equali(toucherclass, "player") || !equali(entityclass, "weaponbox"))
		return FMRES_IGNORED
	
	// Our world model
	if(equali(model, W_MODEL))
	{
		// If allowed to touch
		if(allowed_toucher(ptd))
		{
			// Pick up weapon
			g_hasXbow[ptd] = true
		}
	}
	
	return FMRES_IGNORED;
}

public fw_PlayerKilled_Post(victim, attacker, shouldgib)
{
	if (g_hasXbow[victim])
	{
		touch_fix(victim)
	}
}

// Called on reload
public fw_WeaponReload(iEnt)
{
	// Get ID
	new id = get_pdata_cbase(iEnt, 41, 5)
	
	if (g_hasXbow[id])
	{
		// Set animation
		UTIL_PlayWeaponAnimation(id, crossbow_reload)
		
		// Emit sound
		emit_sound(id, CHAN_WEAPON, XBOW_RELOAD, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
	
		// Update HUD
		UpdateHud(id)
	}
}

// Called when player is using stationary gun(tank)
public fw_UseStationary(entity, caller, activator, use_type)
{
	// Prevent using tank when you're holding crossbow
	new ammo, clip, weapon = get_user_weapon(caller, clip, ammo)
	
	if (use_type == USE_USING && g_hasXbow[caller] && weapon & CSW_AWP)
		return HAM_SUPERCEDE
		
	return HAM_IGNORED
}

// Fire crossbow
public FireCrossbow(id, type)
{
	// Play animation
	UTIL_PlayWeaponAnimation(id, crossbow_fire1)
	
	// Get origin, angle, and velocity
	new Float:fOrigin[3], Float:fAngle[3], Float:fVelocity[3]
	pev(id, pev_origin, fOrigin)
	pev(id, pev_v_angle, fAngle)
	
	// New ent
	new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"));
	
	// Not ent
	if (!ent) return PLUGIN_HANDLED
	
	// If player shot while zooming...
	if (type == 1)
	{
		// Set classnames
		entity_set_string(ent, EV_SZ_classname, "xbow_bolt")
	}
	else
	{
		entity_set_string(ent, EV_SZ_classname, "xbow_bolt_xplode")
	}
	
	// Set bolt model
	entity_set_model(ent, BOLT_MODEL)
	
	// Size
	new Float:mins[3] = {-1.0, -1.0, -1.0}
	new Float:maxs[3] = {1.0, 1.0, 1.0}
	entity_set_size(ent, mins, maxs)
	
	// Movetype
	entity_set_int(ent, EV_INT_movetype, MOVETYPE_FLY)
	
	// Interaction
	entity_set_int(ent, EV_INT_solid, SOLID_TRIGGER)
	
	// Owner
	entity_set_edict(ent, EV_ENT_owner, id)
	
	new Float:origin[3];
	
	// Velocity
	velocity_by_aim(id, 64, fVelocity);
	pev(id, pev_origin, origin);
	
	fOrigin[0] += fVelocity[0];
	fOrigin[1] += fVelocity[1];
	
	// Set origin
	engfunc(EngFunc_SetOrigin, ent, fOrigin)
	
	// Set bolt velocity
	new Float:velocity[3]
	velocity_by_aim(id, 2000, velocity);
	set_pev(ent, pev_velocity, velocity);
	
	// Correct bolt flight
	vector_to_angle(velocity, fAngle);
	
	// Set angle
	set_pev(ent, pev_angles, fAngle);
	
	// Play sound
	emit_sound(id, CHAN_WEAPON, XBOW_SHOOT, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
	
	return PLUGIN_CONTINUE
}

// Normal bolt touched something
public bolt_touch(bolt, ent)
{
	// Invalid ent
	if (!pev_valid(bolt))
		return
	
	// GEt owner and classname	
	new owner = pev(bolt, pev_owner);
	new classname[32]; pev(ent, pev_classname, classname, 31)
	
	// Get it's origin
	new Float:originF[3]
	pev(bolt, pev_origin, originF)
	
	// Loop through all players
	for(new i = 1; i < g_maxplayers; i++)
	{
		// Alive...
		if (is_user_alive(i) == 1 || is_user_connected(i))
		{
			// Zombie/Nemesis
			if (zp_get_user_zombie(i) || zp_get_user_nemesis(i))
			{
				// We hit them
				emit_sound(i, CHAN_WEAPON, XBOW_HITSTAB, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
				
				// Do fake damage
				fakedamage(i, "crossbow", 0.0, DMG_BULLET)
				
				// Get victim origin and distance
				new Float:VictimOrigin[3], Float:distance
				pev(i, pev_origin, VictimOrigin)
				distance = get_distance_f(originF, VictimOrigin)
				
				if (distance <= 17.0)
				{
					// Health/Damage
					new health = get_user_health(i)
					new damage = get_pcvar_num(pDamage2)
					
					if (health - damage >= 1)
						set_user_health(i, health  - damage)
					else
					{
						// Kill them
						user_silentkill(i)
						
						// Log this
						log_kill(owner, i, "crossbow", 0)
						
						// Set new Ammo Packs
						zp_set_user_ammo_packs(owner, zp_get_user_ammo_packs(owner) + 1)
					}
				}
				// Destroy ent
				set_pev(bolt, pev_flags, FL_KILLME)
			}
		}
	}
	// Not a player(wall etc.)
	if (!equal(classname, "player"))
	{
		// Play sound
		emit_sound(bolt, CHAN_WEAPON, XBOW_HITWALL, VOL_NORM, ATTN_NORM, 0, PITCH_NORM)
		
		// Destroy ent
		set_pev(bolt, pev_flags, FL_KILLME)
	}
}
	
// Exploding bolt touched something
public bolt_touch2(bolt2, ent)
{
	// Invalid ent
	if (!pev_valid(bolt2))
		return
	
	// GEt owner and classname	
	new owner = pev(bolt2, pev_owner);
	new classname[32]; pev(ent, pev_classname, classname, 31)
	
	// Get it's origin
	new Float:originV[3]
	pev(bolt2, pev_origin, originV)
	
	// Draw explosion
	message_begin(MSG_BROADCAST, SVC_TEMPENTITY)
	write_byte(TE_EXPLOSION)
	engfunc(EngFunc_WriteCoord, originV[0])
	engfunc(EngFunc_WriteCoord, originV[1])
	engfunc(EngFunc_WriteCoord, originV[2])
	write_short(sExplo)
	write_byte(30)
	write_byte(15)
	write_byte(0)
	message_end()
	
	// Loop through all players
	for(new i = 1; i < g_maxplayers; i++)
	{
		// Alive...
		if (is_user_alive(i) == 1 || is_user_connected(i))
		{
			// Zombie/Nemesis
			if (zp_get_user_zombie(i) || zp_get_user_nemesis(i))
			{
				// Do fake damage
				fakedamage(i, "crossbow", 0.0, DMG_BLAST)
				
				new Float:VictimOrigin[3], Float:distance
				pev(i, pev_origin, VictimOrigin)
				distance = get_distance_f(originV, VictimOrigin)
				
				if (distance <= get_pcvar_float(pRadius))
				{
					new damage = get_pcvar_num(pDamage) + get_pcvar_num(pDamage2)
					new health = get_user_health(i)
					
					if (health - damage >= 1)
						set_user_health(i, health - damage)
					else
					{
						user_silentkill(i)
						
						log_kill(owner, i, "crossbow", 0)
						
						zp_set_user_ammo_packs(owner, zp_get_user_ammo_packs(owner) + 1)
					}
				}
			}
			set_pev(bolt2, pev_flags, FL_KILLME)
		}
	}
	
	// If breakable
	if (equal(classname, "func_breakable"))
	{
		// Use it
		dllfunc(DLLFunc_Use, ent, bolt2)
		set_pev(bolt2, pev_flags, FL_KILLME)
	}
}		

// Touch fix
public touch_fix(id)
{
	if (g_hasXbow[id])
		g_hasXbow[id] = false
}

// Change models
stock ChangeModels(id)
{
	set_pev(id, pev_viewmodel2, V_MODEL);
	set_pev(id, pev_weaponmodel2, P_MODEL);
}

// Play animation(Arkshine)
stock UTIL_PlayWeaponAnimation(const Player, const Sequence)
{
	set_pev(Player, pev_weaponanim, Sequence);
	
	message_begin(MSG_ONE_UNRELIABLE, SVC_WEAPONANIM, .player = Player);
	write_byte(Sequence);
	write_byte(pev(Player, pev_body));
	message_end();
}

// Update HUD
stock UpdateHud(Player)
{
	// This set clip ammo
	message_begin(MSG_ONE_UNRELIABLE, g_msgCurWeapon, .player = Player);
	write_byte(true);
	write_byte(CSW_AWP);
	write_byte(cs_get_weapon_ammo(get_pdata_cbase(Player, 373)))
	message_end()
	
	// This set BP ammo
	message_begin(MSG_ONE_UNRELIABLE, g_msgAmmoX, .player = Player);
	write_byte(CSW_AWP);
	write_byte(cs_get_user_bpammo(Player, CSW_AWP));
	message_end();	
}

// Log kill
stock log_kill(killer, victim, weapon[],headshot) 
{
	set_msg_block(get_user_msgid("DeathMsg"), BLOCK_SET)
	user_kill(victim,1)
	set_msg_block(get_user_msgid("DeathMsg"), BLOCK_NOT)
	
	message_begin(MSG_ALL, get_user_msgid("DeathMsg"), {0,0,0}, 0)
	write_byte(killer)
	write_byte(victim)
	write_byte(headshot)
	write_string(weapon)
	message_end()
	
	if(get_user_team(killer)!=get_user_team(victim))
		set_user_frags(killer,get_user_frags(killer) +1)
	if(get_user_team(killer)==get_user_team(victim))
		set_user_frags(killer,get_user_frags(killer) -1)
	
	new kname[32], vname[32], kauthid[32], vauthid[32], kteam[10], vteam[10]
	
	get_user_name(killer, kname, 31)
	get_user_team(killer, kteam, 9)
	get_user_authid(killer, kauthid, 31)
	
	get_user_name(victim, vname, 31)
	get_user_team(victim, vteam, 9)
	get_user_authid(victim, vauthid, 31)
	
	log_message("^"%s<%d><%s><%s>^" killed ^"%s<%d><%s><%s>^" with ^"%s^"", 
	kname, get_user_userid(killer), kauthid, kteam, 
	vname, get_user_userid(victim), vauthid, vteam, weapon)
	
	return PLUGIN_CONTINUE
}

// Allowed toucher
stock allowed_toucher(player)
{
	// Already has it
	if (g_hasXbow[player])
		return false
	
	return true
}

/*stock fm_get_weapon_id(index, const weapon[])
{
	new ent = -1;
 
	while((ent = engfunc(EngFunc_FindEntityByString, ent, "classname", weapon)) != 0)
	{
		if(index == pev(ent, pev_owner))
		return ent;
	}
	return 1;
}

stock give_weapon(id, wpnID, ammo, bpammo) 
{
	new weapon[32]
	get_weaponname(wpnID, weapon, 31)
    
	give_item(id, weapon)
	cs_set_weapon_ammo(fm_get_weapon_id(id, weapon), ammo);
	cs_set_user_bpammo(id, wpnID, cs_get_user_bpammo(id, wpnID) + bpammo)
}*/
