/*================================================================================

	-----------------------------------------------------------
	-*- [ZPSp] Custom Zombie Skills & Unlocks — Full Rewrite -*-
	-----------------------------------------------------------

	All 6 zombie classes have skills:
		Brute   - Rage Shield: 3s godmode, 25s cooldown
		Hopper  - Double Jump (passive, always active)
		Runner  - Sprint: 3s speed burst to 400, 20s cooldown
		Ghost   - Invisibility: 5s fully invisible, 20s cooldown
		Surgeon - Self Heal: +500 HP instantly, 15s cooldown
		Titan   - Berserker (VIP only, passive): speed scales with HP loss

	F key triggers active skills. Also hooks impulse 100 (flashlight key)
	as a fallback for clients where the auto-bind didn't take effect.

================================================================================*/

#include <amxmodx>
#include <fakemeta>
#include <fun>
#include <zombie_plague_special>

#define PLUGIN_NAME		"[ZPSp] Custom Zombie Skills & Unlocks"
#define PLUGIN_VERSION	"2.0"
#define PLUGIN_AUTHOR	"nogame.ro"

/* ===== Constants ===== */
#define MAX_PLAYERS 32
#define TITAN_VIP_FLAG  (1<<19)   /* flag 't' — founder/VIP */
#define HIT_RESET_TIME  5.0

/* ===== Skill Durations / Cooldowns (cvars) ===== */
new cvar_rage_duration, cvar_rage_cooldown;
new cvar_sprint_duration, cvar_sprint_cooldown, cvar_sprint_speed;
new cvar_invis_duration, cvar_invis_cooldown;
new cvar_heal_amount, cvar_heal_cooldown;

/* ===== Titan Berserker ===== */
new const Float:TITAN_BASE_SPEED = 160.0;
new const Float:TITAN_MAX_SPEED  = 280.0;

/* ===== Class IDs ===== */
new g_class_brute, g_class_hopper, g_class_runner;
new g_class_ghost, g_class_surgeon, g_class_titan;

/* ===== Player State ===== */
new Float:g_rage_cooldown[MAX_PLAYERS + 1];
new bool:g_rage_active[MAX_PLAYERS + 1];

new Float:g_sprint_cooldown[MAX_PLAYERS + 1];
new bool:g_sprint_active[MAX_PLAYERS + 1];

new Float:g_invis_cooldown[MAX_PLAYERS + 1];
new bool:g_invis_active[MAX_PLAYERS + 1];

new Float:g_heal_cooldown[MAX_PLAYERS + 1];

new g_air_jumps[MAX_PLAYERS + 1];   /* Hopper double-jump tracking */

/*================================================================================
 [Init]
=================================================================================*/

public plugin_init()
{
	register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR);

	/* Rage (Brute) */
	cvar_rage_duration = register_cvar("zpsp_rage_duration", "3.0");
	cvar_rage_cooldown = register_cvar("zpsp_rage_cooldown", "25.0");

	/* Sprint (Runner) */
	cvar_sprint_duration = register_cvar("zpsp_sprint_duration", "3.0");
	cvar_sprint_cooldown = register_cvar("zpsp_sprint_cooldown", "20.0");
	cvar_sprint_speed    = register_cvar("zpsp_sprint_speed", "400.0");

	/* Invisibility (Ghost) */
	cvar_invis_duration  = register_cvar("zpsp_invis_duration", "5.0");
	cvar_invis_cooldown  = register_cvar("zpsp_invis_cooldown", "20.0");

	/* Self Heal (Surgeon) */
	cvar_heal_amount     = register_cvar("zpsp_heal_amount", "500");
	cvar_heal_cooldown   = register_cvar("zpsp_heal_cooldown", "15.0");

	/* Command hook (for players whose bind worked) */
	register_clcmd("zombie_skill", "cmd_zombie_skill");

	/* FM_CmdStart: intercept impulse 100 (F/flashlight key) for zombies
	   whose auto-bind didn't take effect. Fires before engine processes it. */
	register_forward(FM_CmdStart, "fw_CmdStart");

	/* PreThink for passives (Hopper jump, Titan berserker) */
	register_forward(FM_PlayerPreThink, "fw_PlayerPreThink");

	/* Cache class IDs after all plugins loaded */
	set_task(0.5, "cache_class_ids");
}

public cache_class_ids()
{
	g_class_brute   = zp_get_zombie_class_id("Brute");
	g_class_hopper  = zp_get_zombie_class_id("Hopper");
	g_class_runner  = zp_get_zombie_class_id("Runner");
	g_class_ghost   = zp_get_zombie_class_id("Ghost");
	g_class_surgeon = zp_get_zombie_class_id("Surgeon");
	g_class_titan   = zp_get_zombie_class_id("Titan");

	server_print("[ZPSp Skills v2] Classes: Brute=%d Hopper=%d Runner=%d Ghost=%d Surgeon=%d Titan=%d",
		g_class_brute, g_class_hopper, g_class_runner, g_class_ghost, g_class_surgeon, g_class_titan);
}

/*================================================================================
 [Connect / Disconnect]
=================================================================================*/

public client_putinserver(id)
{
	if (is_user_bot(id) || is_user_hltv(id))
		return;

	reset_player_state(id);
}

public client_disconnected(id)
{
	reset_player_state(id);
}

reset_player_state(id)
{
	g_rage_active[id] = false;
	g_rage_cooldown[id] = 0.0;
	g_sprint_active[id] = false;
	g_sprint_cooldown[id] = 0.0;
	g_invis_active[id] = false;
	g_invis_cooldown[id] = 0.0;
	g_heal_cooldown[id] = 0.0;
	g_air_jumps[id] = 0;
}

/*================================================================================
 [F Key Handling]
=================================================================================*/

/*
 * FM_CmdStart fires every frame for every player, BEFORE the engine processes
 * usercmds. We check UC_Impulse for 100 (flashlight/F key). If the player is a
 * zombie, we clear the impulse (so no flashlight toggle) and trigger the skill.
 * Humans are left alone (normal flashlight behavior).
 */
public fw_CmdStart(id, uc_handle, random_seed)
{
	if (!is_user_alive(id))
		return FMRES_IGNORED;

	if (get_uc(uc_handle, UC_Impulse) != 100)
		return FMRES_IGNORED;

	if (!zp_get_user_zombie(id))
		return FMRES_IGNORED;   /* Let humans use flashlight normally */

	/* Zombie pressed F — clear impulse so engine doesn't toggle flashlight */
	set_uc(uc_handle, UC_Impulse, 0);

	/* Trigger the skill */
	cmd_zombie_skill(id);

	return FMRES_SUPERCEDE;
}

/*================================================================================
 [Skill Command Router]
=================================================================================*/

public cmd_zombie_skill(id)
{
	if (!is_user_alive(id))
		return PLUGIN_CONTINUE;

	/* Humans: toggle flashlight (F is bound to zombie_skill) */
	if (!zp_get_user_zombie(id))
	{
		client_cmd(id, "impulse 100");
		return PLUGIN_HANDLED;
	}

	/* Disable skills during special rounds */
	if (zp_get_zombie_special_class(id) || zp_get_user_nemesis(id) || zp_get_user_survivor(id))
		return PLUGIN_CONTINUE;

	new classid = zp_get_user_zombie_class(id);
	new Float:now = get_gametime();

	if (classid == g_class_brute)
		return use_rage(id, now);

	if (classid == g_class_runner)
		return use_sprint(id, now);

	if (classid == g_class_ghost)
		return use_invisibility(id, now);

	if (classid == g_class_surgeon)
		return use_self_heal(id, now);

	/* Hopper and Titan have passive skills, no active F ability */
	if (classid == g_class_hopper)
	{
		client_print(id, print_center, "Hopper: Double Jump is passive!");
		return PLUGIN_HANDLED;
	}

	if (classid == g_class_titan)
	{
		if (get_user_flags(id) & TITAN_VIP_FLAG)
			client_print(id, print_center, "Titan: Berserker is passive (faster at low HP)!");
		else
			client_print(id, print_center, "Titan is VIP-only. Berserker inactive.");
		return PLUGIN_HANDLED;
	}

	return PLUGIN_CONTINUE;
}

/*================================================================================
 [Brute — Rage Shield]
=================================================================================*/

use_rage(id, Float:now)
{
	if (g_rage_active[id])
	{
		client_print(id, print_center, "Rage already active!");
		return PLUGIN_HANDLED;
	}

	if (g_rage_cooldown[id] > now)
	{
		client_print(id, print_center, "Rage cooldown: %.1fs", g_rage_cooldown[id] - now);
		return PLUGIN_HANDLED;
	}

	new Float:duration = get_pcvar_float(cvar_rage_duration);
	new Float:cooldown = get_pcvar_float(cvar_rage_cooldown);

	g_rage_active[id] = true;
	g_rage_cooldown[id] = now + cooldown;

	set_user_godmode(id, 1);

	/* Red glow shell */
	set_pev(id, pev_renderfx, kRenderFxGlowShell);
	set_pev(id, pev_rendercolor, Float:{255.0, 0.0, 0.0});
	set_pev(id, pev_rendermode, kRenderTransTexture);
	set_pev(id, pev_renderamt, 50.0);

	client_print(id, print_center, "RAGE SHIELD! Invulnerable for %.1fs", duration);

	set_task(duration, "remove_rage", id);

	return PLUGIN_HANDLED;
}

public remove_rage(id)
{
	if (!is_user_connected(id))
		return;

	g_rage_active[id] = false;

	if (is_user_alive(id) && zp_get_user_zombie(id))
	{
		set_user_godmode(id, 0);
		zp_reset_user_rendering(id);
	}
}

/*================================================================================
 [Runner — Sprint]
=================================================================================*/

use_sprint(id, Float:now)
{
	if (g_sprint_active[id])
	{
		client_print(id, print_center, "Sprint already active!");
		return PLUGIN_HANDLED;
	}

	if (g_sprint_cooldown[id] > now)
	{
		client_print(id, print_center, "Sprint cooldown: %.1fs", g_sprint_cooldown[id] - now);
		return PLUGIN_HANDLED;
	}

	new Float:duration = get_pcvar_float(cvar_sprint_duration);
	new Float:cooldown = get_pcvar_float(cvar_sprint_cooldown);
	new Float:speed    = get_pcvar_float(cvar_sprint_speed);

	g_sprint_active[id] = true;
	g_sprint_cooldown[id] = now + cooldown;

	set_user_maxspeed(id, speed);

	/* Blue glow shell */
	set_pev(id, pev_renderfx, kRenderFxGlowShell);
	set_pev(id, pev_rendercolor, Float:{0.0, 100.0, 255.0});
	set_pev(id, pev_rendermode, kRenderTransTexture);
	set_pev(id, pev_renderamt, 30.0);

	client_print(id, print_center, "SPRINT! Speed boost for %.1fs", duration);

	set_task(duration, "remove_sprint", id);

	return PLUGIN_HANDLED;
}

public remove_sprint(id)
{
	if (!is_user_connected(id))
		return;

	g_sprint_active[id] = false;

	if (is_user_alive(id) && zp_get_user_zombie(id))
	{
		/* Reset to Runner base speed */
		set_user_maxspeed(id, 260.0);
		zp_reset_user_rendering(id);
	}
}

/*================================================================================
 [Ghost — Invisibility]
=================================================================================*/

use_invisibility(id, Float:now)
{
	if (g_invis_active[id])
	{
		client_print(id, print_center, "Invisibility already active!");
		return PLUGIN_HANDLED;
	}

	if (g_invis_cooldown[id] > now)
	{
		client_print(id, print_center, "Invisibility cooldown: %.1fs", g_invis_cooldown[id] - now);
		return PLUGIN_HANDLED;
	}

	new Float:duration = get_pcvar_float(cvar_invis_duration);
	new Float:cooldown = get_pcvar_float(cvar_invis_cooldown);

	g_invis_active[id] = true;
	g_invis_cooldown[id] = now + cooldown;

	/* Fully transparent */
	zp_set_user_rendering(id, kRenderFxNone, 0, 0, 0, kRenderTransTexture, 0);

	client_print(id, print_center, "INVISIBLE for %.1f seconds", duration);

	set_task(duration, "remove_invisibility", id);

	return PLUGIN_HANDLED;
}

public remove_invisibility(id)
{
	if (!is_user_connected(id))
		return;

	g_invis_active[id] = false;

	if (is_user_alive(id) && zp_get_user_zombie(id))
		zp_reset_user_rendering(id);
}

/*================================================================================
 [Surgeon — Self Heal]
=================================================================================*/

use_self_heal(id, Float:now)
{
	if (g_heal_cooldown[id] > now)
	{
		client_print(id, print_center, "Heal cooldown: %.1fs", g_heal_cooldown[id] - now);
		return PLUGIN_HANDLED;
	}

	new amount = get_pcvar_num(cvar_heal_amount);
	new Float:cooldown = get_pcvar_float(cvar_heal_cooldown);

	new Float:health;
	pev(id, pev_health, health);
	new Float:new_health = health + float(amount);

	new max_health = zp_get_zombie_maxhealth(id);
	if (max_health > 0 && new_health > float(max_health))
		new_health = float(max_health);

	set_pev(id, pev_health, new_health);

	g_heal_cooldown[id] = now + cooldown;

	/* Green heal flash */
	set_pev(id, pev_renderfx, kRenderFxGlowShell);
	set_pev(id, pev_rendercolor, Float:{0.0, 255.0, 0.0});
	set_pev(id, pev_rendermode, kRenderTransTexture);
	set_pev(id, pev_renderamt, 40.0);

	client_print(id, print_center, "Healed +%d HP", amount);

	set_task(0.5, "remove_heal_flash", id);

	return PLUGIN_HANDLED;
}

public remove_heal_flash(id)
{
	if (is_user_alive(id) && zp_get_user_zombie(id))
		zp_reset_user_rendering(id);
}

/*================================================================================
 [PreThink — Passive Skills]
=================================================================================*/

public fw_PlayerPreThink(id)
{
	if (!is_user_alive(id) || !zp_get_user_zombie(id))
		return FMRES_IGNORED;

	/* Don't run passives during special rounds */
	if (zp_get_zombie_special_class(id) || zp_get_user_nemesis(id) || zp_get_user_survivor(id))
		return FMRES_IGNORED;

	new classid = zp_get_user_zombie_class(id);

	/* Hopper: Double Jump */
	if (classid == g_class_hopper)
		handle_double_jump(id);

	/* Titan: Berserker (VIP only) */
	if (classid == g_class_titan && (get_user_flags(id) & TITAN_VIP_FLAG))
		update_berserker_speed(id);

	return FMRES_IGNORED;
}

/* ===== Hopper Double Jump ===== */
handle_double_jump(id)
{
	new button = pev(id, pev_button);
	new oldbutton = pev(id, pev_oldbuttons);

	/* Detect fresh jump press (IN_JUMP pressed this frame, not held) */
	if ((button & IN_JUMP) && !(oldbutton & IN_JUMP))
	{
		if (pev(id, pev_flags) & FL_ONGROUND)
		{
			/* On ground — reset air jump counter */
			g_air_jumps[id] = 0;
		}
		else
		{
			/* In air — allow one extra jump */
			if (g_air_jumps[id] < 1)
			{
				new Float:velocity[3];
				pev(id, pev_velocity, velocity);
				velocity[2] = 300.0;   /* Jump force */
				set_pev(id, pev_velocity, velocity);
				g_air_jumps[id]++;

				/* Subtle sound */
				emit_sound(id, CHAN_BODY, "player/pl_wade2.wav", 0.5, ATTN_NORM, 0, PITCH_HIGH);
			}
		}
	}
}

/* ===== Titan Berserker Speed ===== */
update_berserker_speed(id)
{
	/* Don't override during sprint or other speed effects */
	if (g_sprint_active[id] || g_rage_active[id])
		return;

	new Float:health;
	pev(id, pev_health, health);

	new max_hp = zp_get_zombie_maxhealth(id);
	if (max_hp <= 0)
		return;

	new Float:hp_ratio = health / float(max_hp);
	if (hp_ratio > 1.0) hp_ratio = 1.0;
	if (hp_ratio < 0.0) hp_ratio = 0.0;

	/*
	 * Speed scales inversely with HP:
	 *   100% HP → 160 (base, slow tank)
	 *   50% HP  → 220 (moderate)
	 *   25% HP  → 250 (almost Runner speed)
	 *    0% HP  → 280 (matches Runner)
	 */
	new Float:speed = TITAN_BASE_SPEED + (1.0 - hp_ratio) * (TITAN_MAX_SPEED - TITAN_BASE_SPEED);

	set_pev(id, pev_maxspeed, speed);
}
