#pragma once

#include <common/weapons/_all.qh>
#include <common/stats.qh>

float autocvar_g_ballistics_density_corpse;
float autocvar_g_ballistics_density_player;
float autocvar_g_ballistics_mindistance;
bool autocvar_g_ballistics_penetrate_clips = true;
float autocvar_g_ballistics_solidpenetration_exponent = 1;
int autocvar_g_projectiles_newton_style;
float autocvar_g_projectiles_newton_style_2_maxfactor;
float autocvar_g_projectiles_newton_style_2_minfactor;
int autocvar_g_projectiles_spread_style;
int autocvar_g_hitscan_spread_style;
bool autocvar_g_norecoil;
float autocvar_g_trueaim_minrange;

vector w_shotorg;
vector w_shotdir;
vector w_shotend;

// this function calculates w_shotorg and w_shotdir based on the weapon model
// offset, trueaim and antilag, and won't put w_shotorg inside a wall.
// make sure you call makevectors first (FIXME?)
void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range, int deathtype);

#define W_SetupShot_Dir_ProjectileSize(ent, wepent, s_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, deathtype) \
	W_SetupShot_Dir_ProjectileSize_Range(ent, wepent, s_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, max_shot_distance, deathtype)
#define W_SetupShot_ProjectileSize(ent, wepent, mi, ma, antilag, recoil, snd, chan, maxdamage, deathtype) \
	W_SetupShot_Dir_ProjectileSize(ent, wepent, v_forward, mi, ma, antilag, recoil, snd, chan, maxdamage, deathtype)
#define W_SetupShot_Dir(ent, wepent, s_forward, antilag, recoil, snd, chan, maxdamage, deathtype) \
	W_SetupShot_Dir_ProjectileSize(ent, wepent, s_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, deathtype)
#define W_SetupShot(ent, wepent, antilag, recoil, snd, chan, maxdamage, deathtype) \
	W_SetupShot_ProjectileSize(ent, wepent, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, deathtype)
#define W_SetupShot_Range(ent, wepent, antilag, recoil, snd, chan, maxdamage, range, deathtype) \
	W_SetupShot_Dir_ProjectileSize_Range(ent, wepent, v_forward, '0 0 0', '0 0 0', antilag, recoil, snd, chan, maxdamage, range, deathtype)

vector W_CalculateProjectileVelocity(entity actor, vector pvelocity, vector mvelocity, float forceAbsolute);

#if 0
float mspercallsum;
float mspercallsstyle;
float mspercallcount;
#endif

void W_SetupProjVelocity_Explicit(entity proj, vector dir, vector upDir, float pSpeed, float pUpSpeed, float pZSpeed, float spread, float forceAbsolute);

#define W_SetupProjVelocity_Basic(ent,pspeed,pspread) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, pspeed, 0, 0, pspread, false)

#define W_SetupProjVelocity_UP_PRE(ent, wep, prefix) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, WEP_CVAR(wep, prefix##speed), WEP_CVAR(wep, prefix##speed_up), WEP_CVAR(wep, prefix##speed_z), WEP_CVAR(wep, prefix##spread), false)
#define W_SetupProjVelocity_UP_PRI(ent, wep) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, WEP_CVAR_PRI(wep, speed), WEP_CVAR_PRI(wep, speed_up), WEP_CVAR_PRI(wep, speed_z), WEP_CVAR_PRI(wep, spread), false)
#define W_SetupProjVelocity_UP_SEC(ent, wep) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, WEP_CVAR_SEC(wep, speed), WEP_CVAR_SEC(wep, speed_up), WEP_CVAR_SEC(wep, speed_z), WEP_CVAR_SEC(wep, spread), false)

#define W_SetupProjVelocity_UP_BOTH(ent, wep, isprimary) \
	if (isprimary) \
		W_SetupProjVelocity_UP_PRI(ent, wep); \
	else \
		W_SetupProjVelocity_UP_SEC(ent, wep);

#define W_SetupProjVelocity_PRE(ent, wep, prefix) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, WEP_CVAR(wep, prefix##speed), 0, 0, WEP_CVAR(wep, prefix##spread), false)
#define W_SetupProjVelocity_PRI(ent, wep) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, WEP_CVAR_PRI(wep, speed), 0, 0, WEP_CVAR_PRI(wep, spread), false)
#define W_SetupProjVelocity_SEC(ent, wep) \
	W_SetupProjVelocity_Explicit(ent, w_shotdir, v_up, WEP_CVAR_SEC(wep, speed), 0, 0, WEP_CVAR_SEC(wep, spread), false)

#define W_SetupProjVelocity_BOTH(ent, wep, isprimary) \
	if (isprimary) \
		W_SetupProjVelocity_PRI(ent, wep); \
	else \
		W_SetupProjVelocity_SEC(ent, wep);

// ====================
//  Ballistics Tracing
// ====================

.float ballistics_density;

// for railgun damage (hitting multiple enemies)
.bool railgunhit;
.float railgunhitsolidbackup;
.vector railgunhitloc;

IntrusiveList g_railgunhit;
STATIC_INIT(g_railgunhit)
{
	g_railgunhit = IL_NEW();
}

.float railgundistance;
.vector railgunforce;
void FireRailgunBullet(entity this, .entity weaponentity, vector start, vector end, float bdamage, bool headshot_notify, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, int deathtype);

entity fireBullet_trace_callback_eff;
entity fireBullet_last_hit;
void fireBullet_trace_callback(vector start, vector hit, vector end);
void fireBullet_antilag(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float headshot_multiplier, float force, float dtype, entity tracer_effect, bool do_antilag);
void fireBullet(entity this, .entity weaponentity, vector start, vector dir, float spread, float max_solid_penetration, float damage, float headshot_multiplier, float force, float dtype, entity tracer_effect);

void crosshair_trace(entity pl);

void crosshair_trace_plusvisibletriggers(entity pl);
void WarpZone_crosshair_trace_plusvisibletriggers(entity pl);
void crosshair_trace_plusvisibletriggers__is_wz(entity pl, bool is_wz);

void traceline_antilag(entity source, vector v1, vector v2, float nomonst, entity forent, float lag);

void WarpZone_crosshair_trace(entity pl);

void WarpZone_traceline_antilag(entity source, vector v1, vector v2, float nomonst, entity forent, float lag);

IntrusiveList g_ctrace_changed;
STATIC_INIT(g_ctrace_changed)
{
	g_ctrace_changed = IL_NEW();
}
