/*================================================================================
	
	-----------------------------------
	-*- [ZP] Zombie Class: Oxidation Zombie -*-
	-----------------------------------
	
	~~~~~~~~~~~~~~~
	- Description -
	~~~~~~~~~~~~~~~
	New zombie Class Oxidation
	[RU]
	Зомби класс, который имеет способности Оксидная атака, когда он испускает направленный поток газа, и Разложение на оксиды, когда он разлагается,
	оставляя вместо себя облако газа. В любой момент он может вновь стать таким каким был до разложения.

	~~~~~~~~~~~~~~~
	- Changelog -
	~~~~~~~~~~~~~~~
	v. 1.0 - First Released. Full Testing plugin
	v. 1.1 - Added Cvars is from plugin
	v. 1.2 - Used effects to abilities + fix reset control ability
	v. 1.3 - Fix bug Gravity abilitly
	v. 1.4 - Added new effects & sprites
	v. 1.5 - Fixed bug 0 damage for humans and statusicon added
	v. 1.6 - Fix bug & optimizated animation sprites. Version released.
	v. 1.7 - Fix bug Gas Ability No reset countdown & save posiition is Gas Ability
	v. 1.8 - Added Knockback & fix speed bug is gas ability, added new sound
================================================================================*/

#include <amxmodx>
#include <engine>
#include <fakemeta_util>
#include <hamsandwich>
#include <zombieplague>

#define PLUGIN "[ZP] Zombie Class: Oxidation"
#define VERSION "1.8"
#define AUTHOR "Re.Act!ve"

enum (+= 100)
{
	TASK_ATTACK = 3200,
	TASK_GAS,
	TASK_RESET_A,
	TASK_RESET_G,
	TASK_GAS_PUFF, 
}
#define ID_TASK_ATTACK (taskid - TASK_ATTACK)
#define ID_TASK_GAS (taskid - TASK_GAS)
#define ID_TASK_RESET_ATTACK (taskid - TASK_RESET_A)
#define ID_TASK_RESET_GAS (taskid - TASK_RESET_G)
#define ID_TASK_GAS_PUFF (taskid - TASK_GAS_PUFF)

new const zclassT_name[] = { "Оксидант" }
new const zclassT_info[] = { "\r[ADMIN] "}
new const zclassT_model[] = { "IceAdmin" }
new const zclassT_clawmodel[] = { "v_oxid_knife.mdl" }
const zclassT_health = 3500
const zclassT_speed = 265
const Float:zclassT_gravity = 0.7
const Float:zclassT_knockback = 1.2
new oxi_ability[] = 		{ "zm/gas_ability.wav" }	// Звук Оксидной атаки
new oxi_gas[] = 		{ "zm/gas_form.wav" }	// Звук разлагания на газы
new oxi_gas_form[] = 	{ "controller/con_attack2.wav" }	// Звук возврата

new g_Oxi, g_MaxPlayers, g_cdown_attack, g_damage_attack, g_cdown_gas, g_damage_gas, gas_sprite, g_Sprite, g_msgSayText, Float:Time_Attack[33], g_Slow[33]
new bool:g_countdown[33], bool:a_countdown[33], g_msgStatusIcon, g_msgScreenShake, g_msgDamage, g_Cdown[32], a_Cdown[32], bool:g_GasForm[33], g_Sprite_exp, Float:g_gravity[32]
const Float:g_attack_sec = 3.4	// Время действия Оксидной атаки

public plugin_init()
{
	register_plugin(PLUGIN, VERSION, AUTHOR)
	g_cdown_attack = register_cvar("zp_oxi_attack_cdown", "28")	// Перезарядка способности Оксидная атака
	g_damage_attack = register_cvar("zp_oxi_attack_dmg", "1.0")	// Оксидный урон в 0.1 сек
	g_cdown_gas = register_cvar("zp_oxi_gas_cdown", "30")		// Перезарядка способности разложения на газы
	g_damage_gas = register_cvar("zp_oxi_gas_dmg", "8.0")		// Урон наносимый в газовой воронке каждую секунду
	g_MaxPlayers = get_maxplayers( )
	RegisterHam(Ham_Player_Duck, "player", "Player_Duck", 1);
	register_forward(FM_PlayerPreThink, "fw_PlayerPreThink") 
	register_forward(FM_CmdStart, "fw_CmdStart")
	register_clcmd( "buy", "AttackBeginAbility" )	
	register_clcmd( "radio2", "AttackBeginAbility" )	
	register_clcmd( "+attack2", "AttackBeginAbility" )	
	register_clcmd("drop", "GasBeginAbility" )
	register_event("HLTV", "Event_NewRound", "a", "1=0", "2=0");
	// Messages in amxmodx
	g_msgStatusIcon = get_user_msgid("StatusIcon")
	g_msgScreenShake = get_user_msgid("ScreenShake")
	g_msgDamage = get_user_msgid("Damage")
	g_msgSayText = get_user_msgid("SayText")
}

public plugin_precache()
{	
	// Register Class
	g_Oxi = zp_register_zombie_class(zclassT_name, zclassT_info, zclassT_model, zclassT_clawmodel, zclassT_health, zclassT_speed, zclassT_gravity, zclassT_knockback)
	gas_sprite = engfunc(EngFunc_PrecacheModel,"sprites/gas_puff_01.spr")	 			// Спрайт самого газового источника
	g_Sprite = engfunc(EngFunc_PrecacheModel,"sprites/gun_smoke_add.spr")				// Оксидная атака
	g_Sprite_exp = engfunc(EngFunc_PrecacheModel,"sprites/skull.spr")					// Спрайт Оксидное разложение
	precache_sound(oxi_ability)
	precache_sound(oxi_gas)
	precache_sound(oxi_gas_form)
}

public Event_NewRound() 
{
	for ( new id = 1; id < g_MaxPlayers; id++ )
	{	 
		if(g_GasForm[id] && is_user_connected(id))
		{
			if(task_exists(id+TASK_GAS)) remove_task(id+TASK_GAS)
			set_task(0.1, "OriginalForm", id+TASK_GAS)
		}
		g_Slow[id] = false;
		if(task_exists(id+TASK_ATTACK)) remove_task(id+TASK_ATTACK)
		if(task_exists(id+TASK_GAS_PUFF)) remove_task(id+TASK_GAS_PUFF)
	}
}

public zp_user_infected_post(id, infector)
{
	if( (zp_get_user_zombie_class(id) == g_Oxi) && !zp_get_user_nemesis(id) )
	{
		a_countdown[id] = true;
		g_countdown[id] = true;
		ChatColor( id, "^x04[ZP] ^x01У вас есть способности^x04Оксидная атака^x01. и ^x04Разлагание^x01")
		client_cmd(id, "bind T ability1");
	}
}

public zp_user_infected_pre(id)
{
    if(!(get_user_flags(id) & ADMIN_IMMUNITY))
    {
        if (zp_get_user_next_class(id) == g_Oxi)
        {
            zp_set_user_zombie_class(id, 0)
            client_print(id, print_chat, "Выбранный вами класс предназначается для админа или випа. Выбран другой зомби класс.")
        }
    }
}

public  zp_user_humanized_post(id, survivor)
{
	if(g_GasForm[id])
	{
		if(task_exists(id+TASK_GAS)) remove_task(id+TASK_GAS)
		set_task(0.1, "OriginalForm", id+TASK_GAS)	
	}
}

public fw_PlayerPreThink(id)
{
	if(g_Slow[id] && !zp_get_user_zombie(id))
	set_pev( id, pev_maxspeed, 140.0 )

   	if (!is_user_alive(id) || !zp_get_user_zombie(id) )
        return;

	if ((zp_get_user_zombie_class(id) == g_Oxi) && !zp_get_user_nemesis(id) && g_GasForm[id])
	{
		set_pev(id, pev_view_ofs, {0.0, 0.0, -10.0})
		if (pev(id, pev_flags) & FL_ONGROUND)
			set_pev(id, pev_gravity, 999999.9) // set really high
		else
			set_pev(id, pev_gravity, 0.000001) // no gravity
		set_pev( id, pev_maxspeed, 1.0 )
		set_pev(id, pev_velocity, Float:{0.0,0.0,0.0}) // stop motion
	}
}

public AttackBeginAbility(id)
{
	if((zp_get_user_zombie_class(id) == g_Oxi) && !zp_get_user_nemesis(id) && a_countdown[id] && is_user_alive(id))
	{
		a_Cdown[id] = get_pcvar_num(g_cdown_attack);
		Time_Attack[id] = g_attack_sec;
		a_countdown[id] = false;
		emit_sound( id, CHAN_STREAM, oxi_ability, 1.0, ATTN_NORM, 0, PITCH_HIGH )
		set_task(1.0, "ResetAttackAbility", id+TASK_RESET_A)
		set_task(0.1, "OxidAttack", id+TASK_ATTACK, _, _, "a",floatround(Time_Attack[id])*10)
	}
}

public OxidAttack(taskid)
{
	new id = ID_TASK_ATTACK
	if((Time_Attack[id] > 0.0) && zp_get_user_zombie(id) && is_user_alive(id))
	{
		new vec[ 3 ], aimvec[ 3 ], velocityvec[ 3 ]
		new length
		get_user_origin( id, vec )
		get_user_origin( id, aimvec, 2 )
		
		velocityvec[ 0 ] = aimvec[ 0 ] - vec[ 0 ]
		velocityvec[ 1 ] = aimvec[ 1 ] - vec[ 1 ]
		velocityvec[ 2 ] = aimvec[ 2 ] - vec[ 2 ]
		length = sqrt( velocityvec[ 0 ] * velocityvec[ 0 ] + velocityvec[ 1 ] * velocityvec[ 1 ] + velocityvec[ 2 ] * velocityvec[ 2 ] )
		velocityvec[ 0 ] =velocityvec[ 0 ] * 10 / length
		velocityvec[ 1 ] = velocityvec[ 1 ] * 10 / length
		velocityvec[ 2 ] = velocityvec[ 2 ] * 10 / length
		message_begin( MSG_BROADCAST, SVC_TEMPENTITY )
		write_byte( 120 )
		write_coord( vec[ 0 ] )
		write_coord( vec[ 1 ] )
		write_coord( vec[ 2 ] )
		write_coord( velocityvec[ 0 ] )
		write_coord( velocityvec[ 1 ] )
		write_coord( velocityvec[ 2 ] )
		write_short( g_Sprite )
		write_byte( 5 )
		write_byte( 70 )
		write_byte( 100 )
		write_byte( 5 )
		message_end( )
		Time_Attack[id]-=0.1
		DamageBegin(id, vec, aimvec)	
	}
	else
	{
		remove_task(ID_TASK_ATTACK)
	}
}

DamageBegin(id, vec[3], Aimvec[3])
{
	static victim, Float:originAim[3], Float:originF[3]
	new Float:damage; damage = get_pcvar_float(g_damage_attack);
	victim = -1;	IVecFVec(Aimvec, originAim);	IVecFVec(vec, originF)
	new Float:flDistance = get_distance_f (originAim, originF )
	if(flDistance <= 400.0)
	{
		while ((victim = engfunc(EngFunc_FindEntityInSphere, victim, originAim, 38.0)) != 0)
		{
			if (!is_user_alive(victim) || zp_get_user_zombie(victim) || zp_get_user_nemesis(victim))
				continue
	
			do_screen_fade(victim, 0.2, 0.1, 200, 200, 200, 120);
			user_screen_shake(victim, 4, 2, 5)
			ExecuteHam(Ham_TakeDamage, victim, 0, id, damage, DMG_BULLET)	
			message_begin(MSG_ONE_UNRELIABLE, g_msgDamage, _, victim)
			write_byte(0) // damage save
			write_byte(0) // damage take
			write_long(DMG_DROWN) // Freeze
			write_coord(0) // x
			write_coord(0) // y
			write_coord(0) // z
			message_end()
			static Float:flSpeed, Float:vOrigin[3]
			flSpeed = 450.0
			pev(victim, pev_origin, vOrigin)
			static Float:flVelocity [3]
			get_speed_vector(originF, vOrigin, flSpeed, flVelocity)
			           
			set_pev(victim, pev_velocity,flVelocity)
			if(!g_Slow[victim])
			{
				g_Slow[victim] = true;
				set_task(3.0, "ResetSlow", victim);
			}
		}
	}
}

public ResetSlow(id)
{
	g_Slow[id] = false;
	remove_task(id);
}

stock get_speed_vector(const Float:origin1[3],const Float:origin2[3],Float:speed, Float:new_velocity[3])
{
    new_velocity[0] = origin2[0] - origin1[0]
    new_velocity[1] = origin2[1] - origin1[1]
    new_velocity[2] = origin2[2] - origin1[2]
    new Float:num = floatsqroot(speed*speed / (new_velocity[0]*new_velocity[0] + new_velocity[1]*new_velocity[1] + new_velocity[2]*new_velocity[2]))
    new_velocity[0] *= num
    new_velocity[1] *= num
    new_velocity[2] *= num
           
    return 1;
}

public ResetAttackAbility(taskid)
{
	new id = ID_TASK_RESET_ATTACK
	if(a_Cdown[id] > 0 && zp_get_user_zombie(id))
	{
		set_hudmessage(200, 100, 0, 0.7, 0.91, 0, 1.0, 1.1, 0.0, 0.0, -1)
		show_hudmessage(id, "Восстановление ОА: %d",a_Cdown[id])
		a_Cdown[id]-=1
		set_task(1.0, "ResetAttackAbility", id+TASK_RESET_A)
	}
	else
	{
		if(zp_get_user_zombie(id))
		{
			a_countdown[id] = true;
			ChatColor( id, "^x04[ZP] ^x01Перезарядка способности ^x04Оксидная атака^x01 завершена")
		}
		remove_task(ID_TASK_RESET_ATTACK)
	}
}

public ResetGasAbility(taskid)
{
	new id = ID_TASK_RESET_GAS
	if(g_Cdown[id] > 0 && zp_get_user_zombie(id))
	{
		set_hudmessage(200, 100, 0, 0.7, 0.93, 0, 1.0, 1.1, 0.0, 0.0, -1)
		show_hudmessage(id, "Восстановление ОР: %d",g_Cdown[id])
		g_Cdown[id]-=1
		set_task(1.0, "ResetGasAbility", id+TASK_RESET_G)
	}
	else
	{		
		if(zp_get_user_zombie(id))
		{
			g_countdown[id] = true;
			ChatColor( id, "^x04[ZP] ^x01Перезарядка способности ^x04Оксидное Разлагание^x01 завершена")
		}
		remove_task(ID_TASK_RESET_GAS)
	}
}

public client_putinserver(id)
fm_set_user_godmode(id,0)

public GasBeginAbility(id)
{
	if((zp_get_user_zombie_class(id) == g_Oxi) && !zp_get_user_nemesis(id) && g_countdown[id] && is_user_alive(id) && zp_get_user_zombie(id))
	{
		if(!g_GasForm[id])
		{
			new Origin[3]; get_user_origin(id, Origin)
			g_Cdown[id] = get_pcvar_num(g_cdown_gas);
			g_gravity[id] = fm_get_user_gravity(id)
			g_GasForm[id] = true;
			fm_strip_user_weapons(id)
			fm_set_user_godmode(id, 1)
			set_pev(id, pev_view_ofs, {0.0, 0.0, 0.0})
			fm_set_user_rendering(id, kRenderFxGlowShell, 0, 0, 0, kRenderTransAlpha, 0)
			message_begin(MSG_PVS, SVC_TEMPENTITY, Origin)
			write_byte(TE_SPRITE) // TE id
			write_coord(Origin[0]) // x
			write_coord(Origin[1]) // y
			write_coord(Origin[2]+5) // z
			write_short(g_Sprite_exp) // sprite
			write_byte(8) // scale
			write_byte(200) // brightness
			message_end()
			emit_sound( id, CHAN_STREAM, oxi_gas, 1.0, ATTN_NORM, 0, PITCH_HIGH )
			if(task_exists(id+TASK_GAS_PUFF)) remove_task(id+TASK_GAS_PUFF)
			set_task(1.0, "AbilityGasPlayed", id+TASK_GAS_PUFF, "", 0, "b")
			set_task(25.0, "OriginalForm", id+TASK_GAS)
		}
		else
		{
			if(task_exists(id+TASK_GAS)) remove_task(id+TASK_GAS)
			set_task(0.1, "OriginalForm", id+TASK_GAS)
		}
	}
}

public AbilityGasPlayed(taskid)
{
	new id = ID_TASK_GAS_PUFF
	if(g_GasForm[id] && zp_get_user_zombie(id))
	{	
		static Float:originF[3]
		pev(id, pev_origin, originF)
		
		// Get some cvars
		static radius, radius2
		radius = 200 //150
		radius2 = radius-20
		
		//message_begin( MSG_BROADCAST, SVC_TEMPENTITY );
		engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, originF, 0)
		write_byte( TE_FIREFIELD );
		engfunc( EngFunc_WriteCoord, originF[ 0 ] );
		engfunc( EngFunc_WriteCoord, originF[ 1 ] );
		engfunc( EngFunc_WriteCoord, originF[ 2 ] + 50.0 );
		write_short( radius2 );
		write_short( gas_sprite );
		write_byte( 100 );
		write_byte( TEFIRE_FLAG_ALPHA | TEFIRE_FLAG_SOMEFLOAT | TEFIRE_FLAG_LOOP );
		write_byte( 20 );
		message_end();
		
		//message_begin( MSG_BROADCAST, SVC_TEMPENTITY );
		engfunc(EngFunc_MessageBegin, MSG_PVS, SVC_TEMPENTITY, originF, 0)
		write_byte( TE_FIREFIELD );
		engfunc( EngFunc_WriteCoord, originF[ 0 ] );
		engfunc( EngFunc_WriteCoord, originF[ 1 ] );
		engfunc( EngFunc_WriteCoord, originF[ 2 ] + 50.0 );
		write_short( radius );
		write_short( gas_sprite );
		write_byte( 10 );
		write_byte( TEFIRE_FLAG_ALPHA | TEFIRE_FLAG_SOMEFLOAT | TEFIRE_FLAG_LOOP );
		write_byte( 20 );
		message_end( );
		new Float:flVictimOrigin [ 3 ], Float:damage
		for ( new victim = 1; victim < g_MaxPlayers; victim++ )
		{
			pev ( victim, pev_origin, flVictimOrigin )
			new Float:flDistance = get_distance_f (originF, flVictimOrigin )
			if ( flDistance <= 240.0 && !zp_get_user_zombie(victim))
			{
				if (!is_user_alive(victim))
					continue;
	
				g_Slow[victim] = true;
				damage = get_pcvar_float(g_damage_gas)
				fm_fakedamage(victim, "player", damage,  DMG_BLAST)
			}
			else g_Slow[victim] = false;
		}	
	}
	else
	{
		for ( new victim = 1; victim < get_maxplayers(); victim++ )
		g_Slow[victim] = false;

		remove_task(ID_TASK_GAS_PUFF)
	} 
}

public OriginalForm(taskid)
{
	new id = ID_TASK_GAS;
	if(g_GasForm[id])
	{
		g_GasForm[id] = false;
		fm_give_item(id, "weapon_knife")
		set_pev(id, pev_view_ofs, {0.0, 0.0, 26.0})
		fm_set_user_godmode(id, 0)
		fm_set_rendering( id, 0, 0, 0, 0, kRenderNormal, 25 ) 
		emit_sound( id, CHAN_STREAM, oxi_gas_form, 1.0, ATTN_NORM, 0, PITCH_HIGH )
		fm_set_user_gravity(id, g_gravity[id])	
		g_countdown[id] = false;
		set_task(1.0, "ResetGasAbility", id+TASK_RESET_G)
		remove_task(ID_TASK_GAS)
	}
	else remove_task(ID_TASK_GAS)
}

public fw_CmdStart(id, uc_handle, seed)
{
	if (!is_user_alive(id)) return FMRES_IGNORED

	if (zp_get_user_zombie(id) && zp_get_user_zombie_class(id) == g_Oxi)
	{
		if (!g_countdown[id] || !a_countdown[id])
			StatusIcon(id, 2)
		else
			StatusIcon(id, 1)
	}
	else
	{
		StatusIcon(id, 0)
	}
	return FMRES_IGNORED;
}

public Player_Duck(id)
{
	if (g_GasForm[id] && zp_get_user_zombie(id) && zp_get_user_zombie_class(id) == g_Oxi)
	{
   		static button, ducking
   		button = pev(id, pev_button)
		ducking = pev(id, pev_flags) & (FL_DUCKING | FL_ONGROUND) == (FL_DUCKING | FL_ONGROUND)

   		if (button & IN_DUCK || ducking)
		{
			set_pev(id, pev_view_ofs, {0.0, 0.0, 0.0})
   		}
	}
}

StatusIcon(id, run)
{	
	if (!is_user_connected(id) || zp_get_user_nemesis(id)) return;
	
	message_begin(MSG_ONE, g_msgStatusIcon, {0,0,0}, id);
	write_byte(run); // status (0=hide, 1=show, 2=flash)
	write_string("dmg_drown"); // sprite name
	write_byte(110) // red
	write_byte(110) // green
	write_byte(110) // blue
	message_end();
}

ChatColor(target, const message[], any:...)
{
	static buffer[512], i, argscount
	argscount = numargs()
	
	// Send to everyone
	if (!target)
	{
		static player
		for (player = 1; player <= g_MaxPlayers; player++)
		{
			// Not connected
			if (!is_user_connected(player))
				continue;
			
			// Remember changed arguments
			static changed[5], changedcount // [5] = max LANG_PLAYER occurencies
			changedcount = 0
			
			// Replace LANG_PLAYER with player id
			for (i = 2; i < argscount; i++)
			{
				if (getarg(i) == LANG_PLAYER)
				{
					setarg(i, 0, player)
					changed[changedcount] = i
					changedcount++
				}
			}
			
			// Format message for player
			vformat(buffer, charsmax(buffer), message, 3)
			
			// Send it
			message_begin(MSG_ONE_UNRELIABLE, g_msgSayText, _, player)
			write_byte(player)
			write_string(buffer)
			message_end()
			
			// Replace back player id's with LANG_PLAYER
			for (i = 0; i < changedcount; i++)
				setarg(changed[i], 0, LANG_PLAYER)
		}
	}
	// Send to specific target
	else
	{
		// Format message for player
		vformat(buffer, charsmax(buffer), message, 3)
		
		// Send it
		message_begin(MSG_ONE, g_msgSayText, _, target)
		write_byte(target)
		write_string(buffer)
		message_end()
	}
}

stock do_screen_fade(       id, Float:fadeTime, Float:holdTime, red, green, blue, alpha, type = 0x0000       )
{
   static msgScreenFade;
   if (      !msgScreenFade    ) { msgScreenFade = get_user_msgid(    "ScreenFade"           ); }
   new fade, hold;
   fade = clamp(    floatround(     fadeTime * float(1<<12)), 0, 0xFFFF    );
   hold = clamp(     floatround(         holdTime * float(1<<12)), 0, 0xFFFF      );
   message_begin(    MSG_ONE_UNRELIABLE, msgScreenFade, _, id          );
   write_short(          fade    );
   write_short(    hold    );
   write_short(    type   );
   write_byte(      red    );
   write_byte(  green    );
   write_byte(    blue    );
   write_byte(     alpha    );
   message_end(    );
}

user_screen_shake(id, amplitude = 4, duration = 2, frequency = 10)
{
	message_begin(MSG_ONE_UNRELIABLE, g_msgScreenShake, _, id)
	write_short((1<<12)*amplitude) // ??
	write_short((1<<12)*duration) // ??
	write_short((1<<12)*frequency) // ??
	message_end()
}

public sqrt( num )
{
	new div = num
	new result = 1
	while( div > result )
	{
		div = ( div + result ) / 2
		result = num / div
	}
	return div
}