#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <custom_weapons>
#include <cw_stocks>

#define PLUGIN_VERSION	"1.0.3"

new Float:g_fNextLaunch[MAXPLAYERS+1];
new g_iWeaponLauncher[MAXPLAYERS+1] = {-1, ...};
new g_iWeaponViewModel[MAXPLAYERS+1] = {-1, ...};

new Handle:hCvar_ClipSize, iCvar_ClipSize;
new Handle:hCvar_Damage, iCvar_Damage;
new Handle:hCvar_Radius, iCvar_Radius;
new Handle:hCvar_Delay, Float:fCvar_Delay;
new Handle:hCvar_Silencer, bool:bCvar_Silencer;
new Handle:hCvar_ModelFile, String:sCvar_ModelFile[PLATFORM_MAX_PATH];
new Handle:hCvar_SndExplode, String:sCvar_SndExplode[PLATFORM_MAX_PATH];
new Handle:hCvar_SndLaunch, String:sCvar_SndLaunch[PLATFORM_MAX_PATH];

public Plugin:myinfo = 
{
	name = "M4A1 Grenade Launcher",
	author = "FrozDark",
	description = "",
	version = PLUGIN_VERSION,
	url = "www.hlmod.ru"
}

public OnPluginStart()
{
	CreateConVar("sm_m4a1_launcher_version", PLUGIN_VERSION, "Grenade launcher for m4a1 version", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_CHEAT|FCVAR_DONTRECORD);
	
	HookEvent("player_death", OnPlayerDeath);
	HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy);
	
	RegisterOffsets();
	
	hCvar_ClipSize = CreateConVar("sm_m4a1_launcher_clip", "4", "How many grenades in the launcher. 0 to disable and negative value to infinite", FCVAR_PLUGIN);
	iCvar_ClipSize = GetConVarInt(hCvar_ClipSize);
	HookConVarChange(hCvar_ClipSize, OnConVarChange);
	
	hCvar_Silencer = CreateConVar("sm_m4a1_launcher_silencer", "1", "Whether a grenade launcher should be with silencer or not", FCVAR_PLUGIN, true, 0.0, true, 1.0);
	bCvar_Silencer = GetConVarBool(hCvar_Silencer);
	HookConVarChange(hCvar_Silencer, OnConVarChange);
	
	hCvar_Damage = CreateConVar("sm_m4a1_launcher_damage", "80", "How many damage the launcher deal", FCVAR_PLUGIN, true, 0.0);
	iCvar_Damage = GetConVarInt(hCvar_Damage);
	HookConVarChange(hCvar_Damage, OnConVarChange);
	
	hCvar_Radius = CreateConVar("sm_m4a1_launcher_radius", "400", "Grenade launcher explode radius", FCVAR_PLUGIN, true, 0.0);
	iCvar_Radius = GetConVarInt(hCvar_Radius);
	HookConVarChange(hCvar_Radius, OnConVarChange);
	
	hCvar_Delay = CreateConVar("sm_m4a1_launcher_delay", "5.0", "Delay between launch", FCVAR_PLUGIN, true, 0.0);
	fCvar_Delay = GetConVarFloat(hCvar_Delay);
	HookConVarChange(hCvar_Delay, OnConVarChange);
	
	hCvar_ModelFile = CreateConVar("sm_m4a1_launcher_grenade_model", "models/items/ar2_grenade.mdl", "Grenade model", FCVAR_PLUGIN);
	GetConVarString(hCvar_ModelFile, sCvar_ModelFile, sizeof(sCvar_ModelFile));
	HookConVarChange(hCvar_ModelFile, OnConVarChange);
	
	hCvar_SndExplode = CreateConVar("sm_m4a1_launcher_explode_sound", "weapons/explode5.wav", "Explosion sound", FCVAR_PLUGIN);
	GetConVarString(hCvar_SndExplode, sCvar_SndExplode, sizeof(sCvar_SndExplode));
	HookConVarChange(hCvar_SndExplode, OnConVarChange);
	
	hCvar_SndLaunch = CreateConVar("sm_m4a1_launcher_launch_sound", "weapons/grenade_launcher1.wav", "Launch sound", FCVAR_PLUGIN);
	GetConVarString(hCvar_SndLaunch, sCvar_SndLaunch, sizeof(sCvar_SndLaunch));
	HookConVarChange(hCvar_SndLaunch, OnConVarChange);
	
	AutoExecConfig(true, "m4a1_launcher");
}

public OnAllPluginsLoaded()
{
	if (!CW_IsWeaponRegistered("m4a1"))
	{
		CW_RegisterWeapon("m4a1", "models/weapons/m4a1_cod4.mdl", "models/weapons/m4a1_cod4_w_silencer.mdl", OnM4A1Switched);
	}
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual(name, "custom_weapons", false))
	{
		if (!CW_IsWeaponRegistered("m4a1"))
		{
			CW_RegisterWeapon("m4a1", "models/weapons/m4a1_cod4.mdl", "models/weapons/m4a1_cod4_w_silencer.mdl", OnM4A1Switched);
		}
	}
}

public OnConVarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
	if (convar == hCvar_ClipSize)
	{
		iCvar_ClipSize = StringToInt(newValue);
	}
	else if (convar == hCvar_Silencer)
	{
		bCvar_Silencer = bool:StringToInt(newValue);
	}
	else if (convar == hCvar_Damage)
	{
		iCvar_Damage = StringToInt(newValue);
	}
	else if (convar == hCvar_Radius)
	{
		iCvar_Radius = StringToInt(newValue);
	}
	else if (convar == hCvar_Delay)
	{
		fCvar_Delay = StringToFloat(newValue);
	}
	else if (convar == hCvar_SndExplode)
	{
		if (IsModelFile(newValue))
		{
			PrecacheModel(newValue);
			strcopy(sCvar_ModelFile, sizeof(sCvar_ModelFile), newValue);
		}
	}
	else if (convar == hCvar_SndExplode)
	{
		if (IsSoundFile(newValue))
		{
			PrecacheSound(newValue);
			strcopy(sCvar_SndExplode, sizeof(sCvar_SndExplode), newValue);
			
			decl String:buffer[PLATFORM_MAX_PATH];
			FormatEx(buffer, sizeof(buffer), "sound/%s", newValue);
			AddFileToDownloadsTable(buffer);
		}
	}
	else if (convar == hCvar_SndLaunch)
	{
		if (IsSoundFile(newValue))
		{
			PrecacheSound(newValue);
			strcopy(sCvar_SndLaunch, sizeof(sCvar_SndLaunch), newValue);
			
			decl String:buffer[PLATFORM_MAX_PATH];
			FormatEx(buffer, sizeof(buffer), "sound/%s", newValue);
			AddFileToDownloadsTable(buffer);
		}
	}
}

public OnPluginEnd()
{
	CW_UnregisterMe();
}

public OnMapStart()
{
	for (new i = 1; i <= MaxClients; i++)
	{
		g_fNextLaunch[i] = 0.0;
	}
	
	PrecacheModel(sCvar_ModelFile);
}

public OnConfigsExecuted()
{
	decl String:buffer[PLATFORM_MAX_PATH];
	FormatEx(buffer, sizeof(buffer), "sound/%s", sCvar_SndExplode);
	AddFileToDownloadsTable(buffer);
	FormatEx(buffer, sizeof(buffer), "sound/%s", sCvar_SndLaunch);
	AddFileToDownloadsTable(buffer);
	
	PrecacheSound(sCvar_SndExplode);
	PrecacheSound(sCvar_SndLaunch);
}

public OnClientDisconnect_Post(client)
{
	g_fNextLaunch[client] = 0.0;
	g_iWeaponLauncher[client] = -1;
	g_iWeaponViewModel[client] = -1;
}

public OnEntityCreated(entity, const String:classname[])
{
	if (StrEqual(classname, "weapon_m4a1", false))
	{
		SDKHook(entity, SDKHook_SpawnPost, OnM4A1Spawned);
	}
}

public OnM4A1Spawned(entity)
{
	CSWeapon_SetClip2(entity, iCvar_ClipSize);
	
	SetEntProp(entity, Prop_Send, "m_bSilencerOn", bCvar_Silencer);
	new offset = GetEntSendPropOffs(entity, "m_weaponMode");
	if (offset != -1)
	{
		SetEntData(entity, offset, true, true, true);
	}
}

public OnRoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
	new index = -1;
	while ((index = FindEntityByClassname(index, "weapon_m4a1")) != -1)
	{
		CSWeapon_SetClip2(index, iCvar_ClipSize);
	}
}

public OnPlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "userid"));
	
	if (!client || IsPlayerAlive(client))
	{
		return;
	}
	
	g_iWeaponLauncher[client] = -1;
	g_iWeaponViewModel[client] = -1;
}

public Action:OnM4A1Switched(client, weapon, predicted_viewmodel, custom_viewmodel, old_sequence, &sequence, bool:switched_from, &bool:custom_change)
{
	if (!switched_from)
	{
		CSWeapon_SetNextSecondaryAttack(weapon, (GetGameTime() + 999999.0));
		
		g_iWeaponLauncher[client] = weapon;
		g_iWeaponViewModel[client] = predicted_viewmodel;
	}
	else
	{
		g_iWeaponLauncher[client] = -1;
		g_iWeaponViewModel[client] = -1;
	}
}

public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2])
{
	if (g_iWeaponLauncher[client] == -1)
	{
		return Plugin_Continue;
	}
	
	static iPrevButtons[MAXPLAYERS+1];
	
	CSWeapon_SetNextSecondaryAttack(g_iWeaponLauncher[client], (GetGameTime() + 999999.0));
	
	if (buttons & IN_ATTACK2 && !(iPrevButtons[client] & IN_ATTACK2))
	{
		new seq = CSViewModel_GetSequence(g_iWeaponViewModel[client]);
		if (seq != 4 && seq != 5)
		{
			new Float:game_time = GetGameTime();
			if (g_fNextLaunch[client] < game_time && LaunchGrenade(client, g_iWeaponLauncher[client]))
			{
				CSWeapon_SetNextPrimaryAttack(g_iWeaponLauncher[client], game_time + 1.0);
				g_fNextLaunch[client] = game_time + fCvar_Delay;
			}
		}
	}
	
	iPrevButtons[client] = buttons;
	
	return Plugin_Continue;
}

bool:LaunchGrenade(client, weapon)
{
	new clip = CSWeapon_GetClip2(weapon);
	
	if (clip < 1 && iCvar_ClipSize > -1 || bool:GetEntProp(client, Prop_Send, "m_bIsDefusing"))
	{
		return false;
	}
	
	new entity = CreateEntityByName("hegrenade_projectile");
	if (entity == -1)
	{
		LogError("Couldn't create grenade_launcher_projectile");
		return false;
	}
	DispatchKeyValue(entity, "classname", "grenade_launcher_projectile");
	SetEntProp(entity, Prop_Data, "m_nNextThinkTick", -1);
	DispatchSpawn(entity);
	
	CSWeapon_SetClip2(weapon, --clip);
	
	SetEntityModel(entity, sCvar_ModelFile);
	
	EmitSoundToAll(sCvar_SndLaunch, entity, 1, 90);
	
	decl Float:vecOrigin[3];
	decl Float:vecAngles[3];
	decl Float:vecVeloc[3];
	decl Float:end[3];
	
	GetClientEyePosition(client, vecOrigin);
	GetClientEyeAngles(client, vecAngles);
	
	AddInFrontOf(vecOrigin, vecAngles, 40.0, vecOrigin);
	
	GetAngleVectors(vecAngles, vecVeloc, end, NULL_VECTOR);
	ScaleVector(end, 5.0);
	AddVectors(vecOrigin, end, vecOrigin);
	
	TE_SetupMuzzleFlash(vecOrigin, vecAngles, 5.0, 1);
	TE_SendToAll();
	
	ScaleVector(vecVeloc, 1000.0);
	decl Float:baseVel[3];
	GetEntPropVector(client, Prop_Data, "m_vecVelocity", baseVel);
	AddVectors(vecVeloc, baseVel, vecVeloc);
	
	TeleportEntity(entity, vecOrigin, vecAngles, vecVeloc);
	
	SetEntPropVector(entity, Prop_Data, "m_vecAngVelocity", Float:{-500.0, 0.0, 0.0});
	
	CSGrenadeProjectile_SetOwner(entity, client);
	CSGrenadeProjectile_SetTeam(entity, GetClientTeam(client));
	CSGrenadeProjectile_SetElasticity(entity, 0.0);
	
	SetEntProp(entity, Prop_Send, "m_usSolidFlags", 12);
	SetEntProp(entity, Prop_Send, "m_nSolidType", 6);
	SetEntProp(entity, Prop_Send, "m_CollisionGroup", 1);
	
	new SmokeIndex = CreateEntityByName("env_rockettrail");
	if (SmokeIndex != -1)
	{
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_Opacity", 0.5);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_SpawnRate", 100.0);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_ParticleLifetime", 0.5);
		
		SetEntPropVector(SmokeIndex, Prop_Send, "m_StartColor", Float:{0.5, 0.5, 0.5});
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_StartSize", 1.0);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_EndSize", 10.0);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_SpawnRadius", 0.0);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_MinSpeed", 0.0);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_MaxSpeed", 10.0);
		SetEntPropFloat(SmokeIndex, Prop_Send, "m_flFlareScale", 0.5);
		
		DispatchSpawn(SmokeIndex);
		ActivateEntity(SmokeIndex);
		
		TeleportEntity(SmokeIndex, Float:{-10.0,0.0,0.0}, vecAngles, NULL_VECTOR);
		
		SetEntPropEnt(entity, Prop_Data, "m_hMoveChild", SmokeIndex);
		SetEntPropEnt(SmokeIndex, Prop_Data, "m_hMoveParent", entity);
		SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", client);
		SetEntPropEnt(SmokeIndex, Prop_Send, "m_hOwnerEntity", client);
	}
	
	SDKHook(entity, SDKHook_StartTouchPost, OnStartTouchPost);
	
	makecustumviewpunch(client);
	
	return true;
}

public bool:DontHitOwnerOrNade(entity, contentsMask, any:data)
{
	return ((entity != data) && (entity != CSGrenadeProjectile_GetOwner(entity)));
}

public OnStartTouchPost(entity, other)
{
	new client = CSGrenadeProjectile_GetOwner(entity);
	if (client == other)
	{
		return;
	}
	
	decl Float:CheckVec[3];
	CSGrenadeProjectile_GetVelocity(entity, CheckVec);
	if ((CheckVec[0] == 0.0) && (CheckVec[1] == 0.0) && (CheckVec[2] == 0.0) || ((GetEntProp(other, Prop_Send, "m_nSolidType") != 0) && (!(GetEntProp(other, Prop_Send, "m_usSolidFlags") & 0x0004))))
	{
		CreateExplosion(client, entity, CSGrenadeProjectile_GetTeam(entity), iCvar_Damage, iCvar_Radius, "grenade_launcher_projectile");
		
		AcceptEntityInput(entity, "KillHierarchy");
	}
}

CreateExplosion(owner, missile, team, damage, radius, const String:clsName[] = "env_explosion")
{
	new ExplosionIndex = CreateEntityByName("env_explosion");
	if (ExplosionIndex == -1)
	{
		return;
	}
	
	DispatchKeyValue(ExplosionIndex,"classname", clsName);
	
	SetEntProp(ExplosionIndex, Prop_Data, "m_spawnflags", 64);
	SetEntProp(ExplosionIndex, Prop_Data, "m_iMagnitude", damage);
	SetEntProp(ExplosionIndex, Prop_Data, "m_iRadiusOverride", radius);
	
	DispatchSpawn(ExplosionIndex);
	ActivateEntity(ExplosionIndex);
	
	decl Float:pos[3];
	CSGrenadeProjectile_GetOrigin(missile, pos);
	TeleportEntity(ExplosionIndex, pos, NULL_VECTOR, NULL_VECTOR);
	
	SetEntPropEnt(ExplosionIndex, Prop_Send, "m_hOwnerEntity", owner);
	SetEntProp(ExplosionIndex, Prop_Send, "m_iTeamNum", team);
	
	EmitSoundToAll(sCvar_SndExplode, ExplosionIndex, SNDCHAN_WEAPON, SNDLEVEL_NORMAL);
		
	AcceptEntityInput(ExplosionIndex, "Explode");
	
	AcceptEntityInput(ExplosionIndex, "Kill");
}

stock AddInFrontOf(const Float:vecOrigin[3], const Float:vecAngle[3], Float:units, Float:output[3])
{
	decl Float:vecView[3];
	GetAngleVectors(vecAngle, vecView, NULL_VECTOR, NULL_VECTOR);
    
	output[0] = vecView[0] * units + vecOrigin[0];
	output[1] = vecView[1] * units + vecOrigin[1];
	output[2] = vecView[2] * units + vecOrigin[2];
}

stock bool:GetPosition(const Float:vecOrigin[3], const Float:vecAngles[3], Float:output[3], any:data = 0)
{
	new Handle:trace = TR_TraceRayFilterEx(vecOrigin, vecAngles, MASK_SOLID, RayType_Infinite, TraceEntityFilterPlayer, data);
	new bool:result = false;
	
	if (TR_DidHit(trace))
	{
		TR_GetEndPosition(output, trace);
		result = true;
	}
	else
	{
		output[0] = 0.0;
		output[1] = 0.0;
		output[2] = 0.0;
	}
	
	CloseHandle(trace);
	return result;
}

public bool:TraceEntityFilterPlayer(entity, contentsMask, any:data)
{
	return (entity > MaxClients || !entity);
}

stock makecustumviewpunch(client)
{
	decl Float:angle[3];
	
	angle[0] = -12.0;
	angle[1] = GetRandomFloat(-4.0, 4.0);
	angle[2] = 0.0;
	
	makeviewpunch(client, angle);
}

stock makeviewpunch(client, Float:angle[3]){
	
	decl Float:oldangle[3];
	
	GetEntPropVector(client, Prop_Send, "m_vecPunchAngle", oldangle);
	
	oldangle[0] = oldangle[0] + angle[0];
	oldangle[1] = oldangle[1] + angle[1];
	oldangle[2] = oldangle[2] + angle[2];
	
	SetEntPropVector(client, Prop_Send, "m_vecPunchAngle", oldangle);
	SetEntPropVector(client, Prop_Send, "m_vecPunchAngleVel", angle);
}


bool:IsSoundFile(const String:sound[])
{
	decl String:buf[4];
	ZGetExtension(sound, buf, sizeof(buf));
	
	return (!strcmp(buf, "mp3", false) || !strcmp(buf, "wav", false));
}

bool:IsModelFile(const String:model[])
{
	decl String:buf[4];
	ZGetExtension(model, buf, sizeof(buf));
	
	return (!strcmp(buf, "mdl", false));
}

stock ZGetExtension(const String:path[], String:buffer[], size)
{
	new extpos = FindCharInString(path, '.', true);
	
	if (extpos == -1)
	{
		buffer[0] = '\0';
		return;
	}

	strcopy(buffer, size, path[++extpos]);
}