#include <amxmodx>
#include <cstrike>
#include <fakemeta>
#include <hamsandwich>
	
new const VERSION[] =		"2.01";

#define AMMO_FLASHBANG		11
#define AMMO_HEGRENADE		12
#define AMMO_SMOKEGRENADE	13
#define OFFSET_SHIELD		510
#define HAS_SHIELD		(1<<24)
#define USING_SHIELD		(1<<16)
#define DMG_GRENADE		(1<<24) // thanks arkshine
#define FFADE_IN			0x0000 // just here so we don't pass 0 into the function
#define BREAK_GLASS		0x01

#define FROST_RADIUS		240.0
#define NT_FLASHBANG		(1<<0) // 1; CSW:25
#define NT_HEGRENADE		(1<<1) // 2; CSW:4
#define NT_SMOKEGRENADE		(1<<2) // 4; CSW:9
#define TASK_REMOVE_CHILL	100
#define TASK_REMOVE_FREEZE	200
#define TASK_NADE_EXPLODE	300
	
// used by regular_maxspeed near the bottom
new const Float:maxSpeed[31] = { 0.0, 250.0, 0.0, 260.0, 250.0, 240.0, 250.0, 250.0, 240.0, 250.0, 250.0, 250.0,
	250.0, 210.0, 240.0, 240.0, 250.0, 250.0, 210.0, 250.0, 220.0, 230.0, 230.0, 250.0, 210.0, 250.0, 250.0,
	235.0, 221.0, 250.0, 245.0 };

new pcv_enabled, pcv_override, pcv_nadetypes, pcv_teams, pcv_price, pcv_limit, pcv_buyzone, pcv_color,
pcv_by_radius, pcv_hitself, pcv_los, pcv_maxdamage, pcv_mindamage, pcv_chill_maxchance, pcv_chill_minchance,
pcv_chill_duration, pcv_chill_variance, pcv_chill_speed, pcv_freeze_maxchance, pcv_freeze_minchance,
pcv_freeze_duration, pcv_freeze_variance;

new maxPlayers, gmsgCurWeapon, gmsgScreenFade, gmsgStatusIcon, gmsgBlinkAcct, gmsgAmmoPickup, gmsgTextMsg,
gmsgWeapPickup, glassGibs, trailSpr, smokeSpr, exploSpr, mp_friendlyfire, czero, bot_quota, czBotHams, fwdPPT;

new isChilled[33], isFrozen[33], frostKilled[33], novaDisplay[33], Float:oldGravity[33], hasFrostNade[33],
nadesBought[33], Float:flashOver[33];

public plugin_init()
{
	register_plugin("FrostNades",VERSION,"Avalanche");
	register_cvar("fn_version",VERSION,FCVAR_SERVER);

	pcv_enabled = register_cvar("fn_enabled","1");
	pcv_override = register_cvar("fn_override","1");
	pcv_nadetypes = register_cvar("fn_nadetypes","1"); // NT_SMOKEGRENADE
	pcv_teams = register_cvar("fn_teams","3");
	pcv_price = register_cvar("fn_price","300");
	pcv_limit = register_cvar("fn_limit","0");
	pcv_buyzone = register_cvar("fn_buyzone","1");
	pcv_color = register_cvar("fn_color","0 206 209");

	pcv_by_radius = register_cvar("fn_by_radius","0.0");
	pcv_hitself = register_cvar("fn_hitself","0");
	pcv_los = register_cvar("fn_los","1");
	pcv_maxdamage = register_cvar("fn_maxdamage","20.0");
	pcv_mindamage = register_cvar("fn_mindamage","1.0");
	pcv_chill_maxchance = register_cvar("fn_chill_maxchance","100.0");
	pcv_chill_minchance = register_cvar("fn_chill_minchance","100.0");
	pcv_chill_duration = register_cvar("fn_chill_duration","7.0");
	pcv_chill_variance = register_cvar("fn_chill_variance","1.0");
	pcv_chill_speed = register_cvar("fn_chill_speed","60.0");
	pcv_freeze_maxchance = register_cvar("fn_freeze_maxchance","110.0");
	pcv_freeze_minchance = register_cvar("fn_freeze_minchance","40.0");
	pcv_freeze_duration = register_cvar("fn_freeze_duration","4.0");
	pcv_freeze_variance = register_cvar("fn_freeze_variance","0.5");
	
	mp_friendlyfire = get_cvar_pointer("mp_friendlyfire");
	
	new mod[6];
	get_modname(mod,5);
	if(equal(mod,"czero"))
	{
		czero = 1;
		bot_quota = get_cvar_pointer("bot_quota");
	}
	
	maxPlayers = get_maxplayers();
	gmsgCurWeapon = get_user_msgid("CurWeapon");
	gmsgScreenFade = get_user_msgid("ScreenFade");
	gmsgStatusIcon = get_user_msgid("StatusIcon");
	gmsgBlinkAcct = get_user_msgid("BlinkAcct");
	gmsgAmmoPickup = get_user_msgid("AmmoPickup");
	gmsgWeapPickup = get_user_msgid("WeapPickup");
	gmsgTextMsg = get_user_msgid("TextMsg");

	register_event("ScreenFade","event_flashed","be","4=255","5=255","6=255","7>199"); // hit by a flashbang

	register_forward(FM_SetModel,"fw_setmodel",1);
	register_message(get_user_msgid("DeathMsg"),"msg_deathmsg");

	register_event("CurWeapon","event_curweapon","b","1=1");
	register_event("AmmoX","event_ammox","b","1=11","1=12","1=13"); // flash, HE, smoke

	RegisterHam(Ham_Spawn,"player","ham_player_spawn",1);
	RegisterHam(Ham_Killed,"player","ham_player_killed",1);
	RegisterHam(Ham_Think,"grenade","ham_grenade_think",0);
	
	register_clcmd("say /fn","buy_frostnade");
	register_clcmd("say_team /fn","buy_frostnade");
	register_clcmd("say /frostnade","buy_frostnade");
	register_clcmd("say_team /frostnade","buy_frostnade");
}

public plugin_precache()
{
	precache_model("models/frostnova.mdl");
	glassGibs = precache_model("models/glassgibs.mdl");

	precache_sound("warcraft3/frostnova.wav"); // grenade explodes
	precache_sound("warcraft3/impalehit.wav"); // player is frozen
	precache_sound("warcraft3/impalelaunch1.wav"); // frozen wears off
	precache_sound("player/pl_duct2.wav"); // player is chilled
	precache_sound("items/gunpickup2.wav"); // player buys frostnade

	trailSpr = precache_model("sprites/laserbeam.spr");
	smokeSpr = precache_model("sprites/steam1.spr");
	exploSpr = precache_model("sprites/shockwave.spr");
}

public client_putinserver(id)
{
	isChilled[id] = 0;
	isFrozen[id] = 0;
	frostKilled[id] = 0;
	novaDisplay[id] = 0;
	hasFrostNade[id] = 0;
	
	if(czero && !czBotHams && is_user_bot(id) && get_pcvar_num(bot_quota) > 0)
		set_task(0.1,"czbot_hook_ham",id);
}

public client_disconnect(id)
{
	if(isChilled[id]) task_remove_chill(TASK_REMOVE_CHILL+id);
	if(isFrozen[id]) task_remove_freeze(TASK_REMOVE_FREEZE+id);
}

// registering a ham hook for "player" won't register it for CZ bots,
// for some reason. so we have to register it by entity. 
public czbot_hook_ham(id)
{
	if(!czBotHams && is_user_connected(id) && is_user_bot(id) && get_pcvar_num(bot_quota) > 0)
	{
		RegisterHamFromEntity(Ham_Spawn,id,"ham_player_spawn",1);
		RegisterHamFromEntity(Ham_Killed,id,"ham_player_killed",1);
		czBotHams = 1;
	}
}

/****************************************
* PRIMARY FUNCTIONS AND SUCH
****************************************/

public buy_frostnade(id)
{
	if(!get_pcvar_num(pcv_enabled) || get_pcvar_num(pcv_override))
		return PLUGIN_CONTINUE;

	if(!is_user_alive(id)) return PLUGIN_HANDLED;

	if(get_pcvar_num(pcv_buyzone) && !cs_get_user_buyzone(id))
	{
		client_print(id,print_center,"You are not in a buy zone."); // #Cstrike_NotInBuyZone won't work
		return PLUGIN_HANDLED;
	}

	if(!(get_pcvar_num(pcv_teams) & _:cs_get_user_team(id)))
	{
		// have to do it this way to format
		message_begin(MSG_ONE,gmsgTextMsg,_,id);
		write_byte(print_center);
		write_string("#Alias_Not_Avail");
		write_string("Frost Grenade");
		message_end();

		return PLUGIN_HANDLED;
	}

	if(hasFrostNade[id])
	{
		client_print(id,print_center,"#Cstrike_Already_Own_Weapon");
		return PLUGIN_HANDLED;
	}
	
	new limit = get_pcvar_num(pcv_limit);
	if(limit && nadesBought[id] >= limit)
	{
		client_print(id,print_center,"#Cstrike_TitlesTXT_Cannot_Carry_Anymore");
		return PLUGIN_HANDLED;
	}
	
	new money = cs_get_user_money(id), price = get_pcvar_num(pcv_price);

	// need more vespene gas
	if(money < price)
	{
		client_print(id,print_center,"#Cstrike_TitlesTXT_Not_Enough_Money");
		
		message_begin(MSG_ONE_UNRELIABLE,gmsgBlinkAcct,_,id);
		write_byte(2);
		message_end();

		return PLUGIN_HANDLED;
	}
	
	// try to use smokegrenade, then flashbang, then hegrenade
	new wpnid = CSW_SMOKEGRENADE, ammoid = AMMO_SMOKEGRENADE, wpnName[20] = "weapon_smokegrenade", type = get_pcvar_num(pcv_nadetypes);
	if(!(type & NT_SMOKEGRENADE))
	{
		if(type & NT_FLASHBANG)
		{
			wpnid = CSW_FLASHBANG;
			ammoid = AMMO_FLASHBANG;
			wpnName = "weapon_flashbang";
		}
		else if(type & NT_HEGRENADE)
		{
			wpnid = CSW_HEGRENADE;
			ammoid = AMMO_HEGRENADE;
			wpnName = "weapon_hegrenade";
		}
	}
	
	hasFrostNade[id] = wpnid;
	nadesBought[id]++;
	cs_set_user_money(id,money - price);

	new ammo = cs_get_user_bpammo(id,wpnid);

	// give him one
	if(!ammo) ham_give_weapon(id,wpnName);
	else
	{
		cs_set_user_bpammo(id,wpnid,ammo+1);
		
		// just so the player can see what kind it is on his HUD

		message_begin(MSG_ONE,gmsgAmmoPickup,_,id);
		write_byte(ammoid);
		write_byte(ammo+1);
		message_end();

		message_begin(MSG_ONE,gmsgWeapPickup,_,id);
		write_byte(wpnid);
		message_end();
	}

	// won't play via ham_give_weapon or cs_set_user_bpammo
	engfunc(EngFunc_EmitSound,id,CHAN_ITEM,"items/gunpickup2.wav",VOL_NORM,ATTN_NORM,0,PITCH_NORM);
	
	return PLUGIN_HANDLED;
}

// the round ends or starts
public event_round_change()
{
	if(!get_pcvar_num(pcv_enabled)) return;

	for(new i=1;i<=maxPlayers;i++)
	{
		if(is_user_alive(i))
		{
			if(isChilled[i]) task_remove_chill(TASK_REMOVE_CHILL+i);
			if(isFrozen[i]) task_remove_freeze(TASK_REMOVE_FREEZE+i);
		}
	}
}

// the round ends
public event_round_end()
{
	if(!get_pcvar_num(pcv_enabled)) return;

	for(new i=1;i<=maxPlayers;i++)
	{
		if(is_user_alive(i))
		{
			if(isChilled[i]) task_remove_chill(TASK_REMOVE_CHILL+i);
			if(isFrozen[i]) task_remove_freeze(TASK_REMOVE_FREEZE+i);
		}
	}
}

// flashbanged
public event_flashed(id)
{
	// remember end of flashbang
	flashOver[id] = get_gametime() + (float(read_data(1)) / 4096.0);
}

public fw_setmodel(ent,model[])
{
	if(!get_pcvar_num(pcv_enabled)) return FMRES_IGNORED;

	new owner = pev(ent,pev_owner);
	if(!is_user_connected(owner)) return FMRES_IGNORED;
	
	// this isn't going to explode
	new Float:dmgtime;
	pev(ent,pev_dmgtime,dmgtime);
	if(dmgtime == 0.0) return FMRES_IGNORED;
	
	new type, csw;
	if(model[7] == 'w' && model[8] == '_')
	{
		switch(model[9])
		{
			case 'h': { type = NT_HEGRENADE; csw = CSW_HEGRENADE; }
			case 'f': { type = NT_FLASHBANG; csw = CSW_FLASHBANG; }
			case 's': { type = NT_SMOKEGRENADE; csw = CSW_SMOKEGRENADE; }
		}
	}
	if(!type) return FMRES_IGNORED;
	
	new team = _:cs_get_user_team(owner);

	// have a frostnade (override off) ;OR; override enabled, on valid team, using valid frostnade type
	if(hasFrostNade[owner] == csw ||
	(get_pcvar_num(pcv_override) && (get_pcvar_num(pcv_teams) & team) && (get_pcvar_num(pcv_nadetypes) & type)))
	{
		hasFrostNade[owner] = 0;

		set_pev(ent,pev_team,team);
		set_pev(ent,pev_bInDuck,1); // flag it as a frostnade

		new Float:rgb[3];
		get_rgb_colors(team,rgb);

		set_glowshell(ent,rgb,16.0);
		set_beamfollow(ent,10,10,rgb,100);
	}

	return FMRES_IGNORED;
}

// freeze a player in place whilst he's frozen
public fw_playerprethink(id)
{
	if(isFrozen[id])
	{
		set_pev(id,pev_velocity,Float:{0.0,0.0,0.0}); // stop motion
		
		engfunc(EngFunc_SetClientMaxspeed,id,0.00001); // keep immobile
		
		new Float:gravity;
		pev(id,pev_gravity,gravity);
		
		// remember any gravity changes
		if(gravity != 0.000000001 && gravity != 999999999.9)
			oldGravity[id] = gravity;

		// if are on the ground and about to jump, set the gravity too high to really do so
		if((pev(id,pev_button) & IN_JUMP) && !(pev(id,pev_oldbuttons) & IN_JUMP) && (pev(id,pev_flags) & FL_ONGROUND))
			set_pev(id,pev_gravity,999999999.9);

		// otherwise, set the gravity so low that they don't fall
		else set_pev(id,pev_gravity,0.000000001);
	}
	
	return FMRES_IGNORED;
}

// override grenade kill message with skull and crossbones
public msg_deathmsg(msg_id,msg_dest,msg_entity)
{
	new victim = get_msg_arg_int(2);
	if(!is_user_connected(victim) || !frostKilled[victim]) return PLUGIN_CONTINUE;

	static weapon[8];
	get_msg_arg_string(4,weapon,7);
	if(equal(weapon,"grenade")) set_msg_arg_string(4,"frostgrenade");

	frostKilled[victim] = 0;
	return PLUGIN_CONTINUE;
}

// maintain speed on weapon changes
public event_curweapon(id)
{
	if(get_pcvar_num(pcv_enabled) && is_user_connected(id))
	{
		if(isChilled[id]) set_maxspeed(id);
		manage_icon(id);
	}
}

// a player's grenade ammo changes
public event_ammox(id)
{
	if(get_pcvar_num(pcv_enabled) && is_user_connected(id))
	{
		if(hasFrostNade[id] && !cs_get_user_bpammo(id,hasFrostNade[id])) hasFrostNade[id] = 0; // just lost my frost grenade
		manage_icon(id);
	}
}

// rezzed
public ham_player_spawn(id)
{
	nadesBought[id] = 0;
	
	if(is_user_alive(id))
	{
		if(isChilled[id]) task_remove_chill(TASK_REMOVE_CHILL+id);
		if(isFrozen[id]) task_remove_freeze(TASK_REMOVE_FREEZE+id);
	}
}

// killed to death
public ham_player_killed(id)
{
	// these two should technically be caught by ammox first
	hasFrostNade[id] = 0;
	manage_icon(id);

	if(isChilled[id]) task_remove_chill(TASK_REMOVE_CHILL+id);
	if(isFrozen[id]) task_remove_freeze(TASK_REMOVE_FREEZE+id);
}

// grenade is ticking away
public ham_grenade_think(ent)
{
	// not a frostnade
	if(!pev(ent,pev_bInDuck)) return FMRES_IGNORED;
	
	new Float:dmgtime;
	pev(ent,pev_dmgtime,dmgtime);
	if(dmgtime > get_gametime()) return FMRES_IGNORED;
	
	// and boom goes the dynamite
	frostnade_explode(ent);

	return FMRES_SUPERCEDE;
}

// a frost grenade explodes
public frostnade_explode(ent)
{
	new nadeTeam = pev(ent,pev_team), owner = pev(ent,pev_owner), Float:nadeOrigin[3];
	pev(ent,pev_origin,nadeOrigin);
	
	// make the smoke
	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_SMOKE);
	write_coord(floatround(nadeOrigin[0])); // x
	write_coord(floatround(nadeOrigin[1])); // y
	write_coord(floatround(nadeOrigin[2])); // z
	write_short(smokeSpr); // sprite
	write_byte(random_num(30,40)); // scale
	write_byte(5); // framerate
	message_end();
	
	// explosion
	create_blast(nadeTeam,nadeOrigin);
	emit_sound(ent,CHAN_BODY,"warcraft3/frostnova.wav",VOL_NORM,ATTN_NORM,0,PITCH_NORM);

	// cache our cvars
	new ff = get_pcvar_num(mp_friendlyfire), Float:by_radius = get_pcvar_float(pcv_by_radius),
	hitself = get_pcvar_num(pcv_hitself), los = get_pcvar_num(pcv_los), Float:maxdamage = get_pcvar_float(pcv_maxdamage),
	Float:mindamage = get_pcvar_float(pcv_mindamage), Float:chill_maxchance = get_pcvar_float(pcv_chill_maxchance),
	Float:chill_minchance = get_pcvar_float(pcv_chill_minchance), Float:freeze_maxchance, Float:freeze_minchance;

	if(!by_radius)
	{
		freeze_maxchance = get_pcvar_float(pcv_freeze_maxchance);
		freeze_minchance = get_pcvar_float(pcv_freeze_minchance);
	}

	new ta, Float:targetOrigin[3], Float:distance, tr = create_tr2(), Float:fraction, Float:damage, gotFrozen;
	for(new target=1;target<=maxPlayers;target++)
	{
		// dead, invincible, or self attack that is not allowed
		if(!is_user_alive(target) || pev(target,pev_takedamage) == DAMAGE_NO
		|| (pev(target,pev_flags) & FL_GODMODE) ||(target == owner && !hitself))
			continue;
		
		// this is a team attack with ff disabled, excluding self attack
		ta = (_:cs_get_user_team(target) == nadeTeam);
		if(ta && !ff && target != owner) continue;
		
		pev(target,pev_origin,targetOrigin);
		distance = vector_distance(nadeOrigin,targetOrigin);
		
		// too far
		if(distance > FROST_RADIUS) continue;

		// check line of sight
		if(los)
		{
			nadeOrigin[2] += 2.0;
			engfunc(EngFunc_TraceLine,nadeOrigin,targetOrigin,DONT_IGNORE_MONSTERS,ent,tr);
			nadeOrigin[2] -= 2.0;

			get_tr2(tr,TR_flFraction,fraction);
			if(fraction != 1.0 && get_tr2(tr,TR_pHit) != target) continue;
		}

		// damaged
		if(maxdamage > 0.0)
		{
			damage = radius_calc(distance,FROST_RADIUS,maxdamage,mindamage);
			if(ta) damage /= 2.0; // half damage for friendlyfire

			if(damage > 0.0)
			{
				frostKilled[target] = 1;
				ExecuteHamB(Ham_TakeDamage,target,ent,owner,damage,DMG_GRENADE);
				if(!is_user_alive(target)) continue; // dead now
				frostKilled[target] = 0;
			}
		}

		// frozen
		if((by_radius && radius_calc(distance,FROST_RADIUS,100.0,0.0) >= by_radius)
		|| (!by_radius && random_num(1,100) <= floatround(radius_calc(distance,FROST_RADIUS,freeze_maxchance,freeze_minchance))))
		{
			gotFrozen = 1;

			freeze_player(target,nadeTeam);
			emit_sound(target,CHAN_BODY,"warcraft3/impalehit.wav",VOL_NORM,ATTN_NORM,0,PITCH_HIGH);
		}
		else gotFrozen = 0;
		
		// chilled
		if(by_radius || random_num(1,100) <= floatround(radius_calc(distance,FROST_RADIUS,chill_maxchance,chill_minchance)))
		{
			chill_player(target,nadeTeam);
			if(!gotFrozen) emit_sound(target,CHAN_BODY,"player/pl_duct2.wav",VOL_NORM,ATTN_NORM,0,PITCH_LOW);
		}
	}

	free_tr2(tr);
	set_pev(ent,pev_flags,pev(ent,pev_flags)|FL_KILLME);
}

freeze_player(id,nadeTeam)
{
	isFrozen[id] = nadeTeam;
	oldGravity[id] = 1.0;
	
	set_pev(id,pev_velocity,Float:{0.0,0.0,0.0});
	
	// register our forward only when we need it
	if(!fwdPPT) fwdPPT = register_forward(FM_PlayerPreThink,"fw_playerprethink",0);
	
	new Float:duration = get_pcvar_float(pcv_freeze_duration), Float:variance = get_pcvar_float(pcv_freeze_variance);
	duration += random_float(-variance,variance);

	remove_task(TASK_REMOVE_FREEZE+id);
	set_task(duration,"task_remove_freeze",TASK_REMOVE_FREEZE+id);
	
	if(!pev_valid(novaDisplay[id])) create_nova(id);
}

public task_remove_freeze(taskid)
{
	new id = taskid-TASK_REMOVE_FREEZE;
	
	if(pev_valid(novaDisplay[id]))
	{
		new origin[3], Float:originF[3];
		pev(novaDisplay[id],pev_origin,originF);
		FVecIVec(originF,origin);

		// add some tracers
		message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
		write_byte(TE_IMPLOSION);
		write_coord(origin[0]); // x
		write_coord(origin[1]); // y
		write_coord(origin[2] + 8); // z
		write_byte(64); // radius
		write_byte(10); // count
		write_byte(3); // duration
		message_end();

		// add some sparks
		message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
		write_byte(TE_SPARKS);
		write_coord(origin[0]); // x
		write_coord(origin[1]); // y
		write_coord(origin[2]); // z
		message_end();

		// add the shatter
		message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
		write_byte(TE_BREAKMODEL);
		write_coord(origin[0]); // x
		write_coord(origin[1]); // y
		write_coord(origin[2] + 24); // z
		write_coord(16); // size x
		write_coord(16); // size y
		write_coord(16); // size z
		write_coord(random_num(-50,50)); // velocity x
		write_coord(random_num(-50,50)); // velocity y
		write_coord(25); // velocity z
		write_byte(10); // random velocity
		write_short(glassGibs); // model
		write_byte(10); // count
		write_byte(25); // life
		write_byte(BREAK_GLASS); // flags
		message_end();

		emit_sound(novaDisplay[id],CHAN_BODY,"warcraft3/impalelaunch1.wav",VOL_NORM,ATTN_NORM,0,PITCH_LOW);
		set_pev(novaDisplay[id],pev_flags,pev(novaDisplay[id],pev_flags)|FL_KILLME);
	}

	isFrozen[id] = 0;
	novaDisplay[id] = 0;
	
	// unregister forward if we are no longer using it
	if(fwdPPT)
	{
		new i;
		for(i=1;i<=maxPlayers;i++) if(isFrozen[i]) break;
		if(i > maxPlayers)
		{
			unregister_forward(FM_PlayerPreThink,fwdPPT,0);
			fwdPPT = 0;
		}
	}

	if(!is_user_connected(id)) return;
	
	set_maxspeed(id);
	set_pev(id,pev_gravity,oldGravity[id]);
	
	// sometimes trail fades during freeze, reapply
	if(isChilled[id])
	{
		new Float:rgb[3];
		get_rgb_colors(isChilled[id],rgb);
		set_beamfollow(id,30,8,rgb,100);
	}
	else
	{
		// let other plugins change speed if they need to
		new clip, weapon = get_user_weapon(id,clip);
		emessage_begin(MSG_ONE,gmsgCurWeapon,_,id);
		ewrite_byte(1); // is current
		ewrite_byte(weapon); // weapon id
		ewrite_byte(clip); // clip ammo
		emessage_end();
	}
}

chill_player(id,nadeTeam)
{
	isChilled[id] = nadeTeam;
	
	set_maxspeed(id);
	
	new Float:duration = get_pcvar_float(pcv_chill_duration), Float:variance = get_pcvar_float(pcv_chill_variance);
	duration += random_float(-variance,variance);

	remove_task(TASK_REMOVE_CHILL+id);
	set_task(duration,"task_remove_chill",TASK_REMOVE_CHILL+id);

	new Float:rgb[3];
	get_rgb_colors(nadeTeam,rgb);

	set_glowshell(id,rgb,1.0);
	set_beamfollow(id,30,8,rgb,100);

	// I decided to let the frostnade tint override a flashbang,
	// because if you are frozen, then you have much bigger problems.

	// add a blue tint to their screen
	message_begin(MSG_ONE,gmsgScreenFade,_,id);
	write_short(floatround(4096.0 * duration)); // duration
	write_short(floatround(3072.0 * duration)); // hold time (4096.0 * 0.75)
	write_short(FFADE_IN); // flags
	write_byte(floatround(rgb[0])); // red
	write_byte(floatround(rgb[1])); // green
	write_byte(floatround(rgb[2])); // blue
	write_byte(100); // alpha
	message_end();
}

public task_remove_chill(taskid)
{
	new id = taskid-TASK_REMOVE_CHILL;

	isChilled[id] = 0;
	if(!is_user_connected(id)) return;
	
	set_maxspeed(id);

	clear_glowshell(id);
	clear_beamfollow(id);
	
	// not blinded
	if(get_gametime() >= flashOver[id])
	{
		// clear tint
		message_begin(MSG_ONE,gmsgScreenFade,_,id);
		write_short(0); // duration
		write_short(0); // hold time
		write_short(FFADE_IN); // flags
		write_byte(0); // red
		write_byte(0); // green
		write_byte(0); // blue
		write_byte(255); // alpha
		message_end();
	}
	
	if(!isFrozen[id])
	{
		// let other plugins change speed if they need to
		new clip, weapon = get_user_weapon(id,clip);
		emessage_begin(MSG_ONE,gmsgCurWeapon,_,id);
		ewrite_byte(1); // is current
		ewrite_byte(weapon); // weapon id
		ewrite_byte(clip); // clip ammo
		emessage_end();
	}
}

// set my maxspeed to whatever it should be
set_maxspeed(id)
{
	if(isFrozen[id]) return engfunc(EngFunc_SetClientMaxspeed,id,0.00001);

	new Float:speed = regular_maxspeed(id);
	if(isChilled[id]) speed *= (get_pcvar_float(pcv_chill_speed) / 100.0);
	return engfunc(EngFunc_SetClientMaxspeed,id,speed);
}

// make a frost nova at a player's feet
create_nova(id)
{
	new nova = engfunc(EngFunc_CreateNamedEntity,engfunc(EngFunc_AllocString,"info_target"));

	engfunc(EngFunc_SetSize,nova,Float:{-8.0,-8.0,-4.0},Float:{8.0,8.0,4.0});
	engfunc(EngFunc_SetModel,nova,"models/frostnova.mdl");

	// random orientation
	new Float:angles[3];
	angles[1] = random_float(0.0,360.0);
	set_pev(nova,pev_angles,angles);

	// put it at their feet
	new Float:playerMins[3], Float:novaOrigin[3];
	pev(id,pev_mins,playerMins);
	pev(id,pev_origin,novaOrigin);
	novaOrigin[2] += playerMins[2];
	engfunc(EngFunc_SetOrigin,nova,novaOrigin);

	// make it translucent
	get_rgb_colors(isFrozen[id],angles); // let's just use angles
	set_pev(nova,pev_rendercolor,angles); // ^
	set_pev(nova,pev_rendermode,kRenderTransColor);
	set_pev(nova,pev_renderamt,100.0);

	novaDisplay[id] = nova;
}

// manage our snowflake (show it, hide it, flash it?)
manage_icon(id)
{
	new status, team = _:cs_get_user_team(id);

	// so if I have it, status = 1; if I also have it out, status = 2
	if(get_pcvar_num(pcv_enabled))
	{
		if(hasFrostNade[id])
		{
			status = 1;
			if(get_user_weapon(id) == hasFrostNade[id]) status = 2;
		}
		else if(get_pcvar_num(pcv_override) && (get_pcvar_num(pcv_teams) & team))
		{
			new weapon = get_user_weapon(id), types = get_pcvar_num(pcv_nadetypes);

			if(types & NT_HEGRENADE)
			{
				if(cs_get_user_bpammo(id,CSW_HEGRENADE))
				{
					status = 1;
					if(weapon == CSW_HEGRENADE) status = 2;
				}
			}
			if(status != 2 && (types & NT_SMOKEGRENADE))
			{
				if(cs_get_user_bpammo(id,CSW_SMOKEGRENADE))
				{
					status = 1;
					if(weapon == CSW_SMOKEGRENADE) status = 2;
				}
			}
			if(status != 2 && (types & NT_FLASHBANG))
			{
				if(cs_get_user_bpammo(id,CSW_FLASHBANG))
				{
					status = 1;
					if(weapon == CSW_FLASHBANG) status = 2;
				}
			}
		}
	}
	
	new Float:rgb[3];
	if(status) get_rgb_colors(team,rgb); // only get colors if we need to
	
	message_begin(MSG_ONE_UNRELIABLE,gmsgStatusIcon,_,id);
	write_byte(status); // status (0=hide, 1=show, 2=flash)
	write_string("dmg_cold"); // sprite name
	write_byte(floatround(rgb[0])); // red
	write_byte(floatround(rgb[1])); // green
	write_byte(floatround(rgb[2])); // blue
	message_end();
}

/****************************************
* UTILITY FUNCTIONS
****************************************/

// make the explosion effects
create_blast(team,Float:originF[3])
{
	new origin[3];
	FVecIVec(originF,origin);

	new Float:rgbF[3], rgb[3];
	get_rgb_colors(team,rgbF);
	FVecIVec(rgbF,rgb);

	// smallest ring
	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_BEAMCYLINDER);
	write_coord(origin[0]); // x
	write_coord(origin[1]); // y
	write_coord(origin[2]); // z
	write_coord(origin[0]); // x axis
	write_coord(origin[1]); // y axis
	write_coord(origin[2] + 385); // z axis
	write_short(exploSpr); // sprite
	write_byte(0); // start frame
	write_byte(0); // framerate
	write_byte(4); // life
	write_byte(60); // width
	write_byte(0); // noise
	write_byte(rgb[0]); // red
	write_byte(rgb[1]); // green
	write_byte(rgb[2]); // blue
	write_byte(100); // brightness
	write_byte(0); // speed
	message_end();

	// medium ring
	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_BEAMCYLINDER);
	write_coord(origin[0]); // x
	write_coord(origin[1]); // y
	write_coord(origin[2]); // z
	write_coord(origin[0]); // x axis
	write_coord(origin[1]); // y axis
	write_coord(origin[2] + 470); // z axis
	write_short(exploSpr); // sprite
	write_byte(0); // start frame
	write_byte(0); // framerate
	write_byte(4); // life
	write_byte(60); // width
	write_byte(0); // noise
	write_byte(rgb[0]); // red
	write_byte(rgb[1]); // green
	write_byte(rgb[2]); // blue
	write_byte(100); // brightness
	write_byte(0); // speed
	message_end();

	// largest ring
	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_BEAMCYLINDER);
	write_coord(origin[0]); // x
	write_coord(origin[1]); // y
	write_coord(origin[2]); // z
	write_coord(origin[0]); // x axis
	write_coord(origin[1]); // y axis
	write_coord(origin[2] + 555); // z axis
	write_short(exploSpr); // sprite
	write_byte(0); // start frame
	write_byte(0); // framerate
	write_byte(4); // life
	write_byte(60); // width
	write_byte(0); // noise
	write_byte(rgb[0]); // red
	write_byte(rgb[1]); // green
	write_byte(rgb[2]); // blue
	write_byte(100); // brightness
	write_byte(0); // speed
	message_end();

	// light effect
	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_DLIGHT);
	write_coord(origin[0]); // x
	write_coord(origin[1]); // y
	write_coord(origin[2]); // z
	write_byte(floatround(FROST_RADIUS/5.0)); // radius
	write_byte(rgb[0]); // r
	write_byte(rgb[1]); // g
	write_byte(rgb[2]); // b
	write_byte(8); // life
	write_byte(60); // decay rate
	message_end();
}

// give an entity a glowshell
set_glowshell(ent,Float:rgb[3],Float:amount)
{
	set_pev(ent,pev_renderfx,kRenderFxGlowShell);
	set_pev(ent,pev_rendercolor,rgb);
	set_pev(ent,pev_renderamt,amount);
}

// take away the glowshell
clear_glowshell(ent)
{
	set_pev(ent,pev_renderfx,kRenderFxNone);
	set_pev(ent,pev_rendercolor,Float:{255.0,255.0,255.0});
	set_pev(ent,pev_renderamt,16.0);
}

// give an entity a beam trail
set_beamfollow(ent,life,width,Float:rgb[3],brightness)
{
	clear_beamfollow(ent);

	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_BEAMFOLLOW);
	write_short(ent); // entity
	write_short(trailSpr); // sprite
	write_byte(life); // life
	write_byte(width); // width
	write_byte(floatround(rgb[0])); // red
	write_byte(floatround(rgb[1])); // green
	write_byte(floatround(rgb[2])); // blue
	write_byte(brightness); // brightness
	message_end();
}

// removes beam trails from an entity
clear_beamfollow(ent)
{
	message_begin(MSG_BROADCAST,SVC_TEMPENTITY);
	write_byte(TE_KILLBEAM);
	write_short(ent); // entity
	message_end();
}	

// gets the regular maxspeed for this player based on his weapon
Float:regular_maxspeed(id)
{
	if(!is_user_alive(id)) return 0.0;
	
	new shield = get_pdata_int(id,OFFSET_SHIELD);
	if(shield & USING_SHIELD) return 180.0;
	if(shield & HAS_SHIELD) return 250.0;
	
	new weapon = get_user_weapon(id);
	if(weapon < 0 || weapon > 30) return 0.0;
	
	return maxSpeed[weapon];
}

// gets RGB colors from the cvar
get_rgb_colors(team,Float:rgb[3])
{
	static color[12], parts[3][4];
	get_pcvar_string(pcv_color,color,11);
	
	// if cvar is set to "team", use colors based on the given team
	if(equali(color,"team",4))
	{
		if(team == 1)
		{
			rgb[0] = 150.0;
			rgb[1] = 0.0;
			rgb[2] = 0.0;
		}
		else
		{
			rgb[0] = 0.0;
			rgb[1] = 0.0;
			rgb[2] = 150.0;
		}
	}
	else
	{
		parse(color,parts[0],3,parts[1],3,parts[2],3);
		rgb[0] = floatstr(parts[0]);
		rgb[1] = floatstr(parts[1]);
		rgb[2] = floatstr(parts[2]);
	}
}

// scale a value equally (inversely?) with the distance that something
// is from the center of another thing. that makes pretty much no sense,
// so basically, the closer we are to the center of a ring, the higher
// our value gets.
//
// EXAMPLE: distance = 60.0, radius = 240.0, maxVal = 100.0, minVal = 20.0
// we are 0.75 (1.0-(60.0/240.0)) of the way to the radius, so scaled with our
// values, it comes out to 80.0 (20.0 + (0.75 * (100.0 - 20.0)))
Float:radius_calc(Float:distance,Float:radius,Float:maxVal,Float:minVal)
{
	if(maxVal <= 0.0) return 0.0;
	if(minVal >= maxVal) return minVal;
	return minVal + ((1.0 - (distance / radius)) * (maxVal - minVal));
}

// gives a player a weapon efficiently
stock ham_give_weapon(id,weapon[])
{
	if(!equal(weapon,"weapon_",7)) return 0;

	new wEnt = engfunc(EngFunc_CreateNamedEntity,engfunc(EngFunc_AllocString,weapon));
	if(!pev_valid(wEnt)) return 0;

 	set_pev(wEnt,pev_spawnflags,SF_NORESPAWN);
	dllfunc(DLLFunc_Spawn,wEnt);
	
	if(!ExecuteHamB(Ham_AddPlayerItem,id,wEnt))
	{
		if(pev_valid(wEnt)) set_pev(wEnt,pev_flags,pev(wEnt,pev_flags) | FL_KILLME);
		return 0;
	}

	ExecuteHamB(Ham_Item_AttachToPlayer,wEnt,id)
	return 1;
}