/*
   Copyright (C) 1997-2001 Id Software, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

   See the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */
// g_weapon.c

#include "g_local.h"
#include "g_gametypes.h"


static qboolean	is_quad;


//======================================================================
//
//	NEW WEAPON SYSTEM
//
//======================================================================

//===============
//Pickup_Weapon
//===============
qboolean Pickup_Weapon( edict_t *ent, edict_t *other )
{
	int ammo_tag;

	if( ( dmflags->integer & DF_WEAPONS_STAY ) && other->r.client->inventory[ent->item->tag] )
	{
		if( !( ent->spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) )
			return qfalse; // leave the weapon for others to pickup
	}

	other->r.client->inventory[ent->item->tag]++;

	// never allow the player to carry more than 2 copies of the same weapon
	if( other->r.client->inventory[ent->item->tag] > 2 )
		other->r.client->inventory[ent->item->tag] = 2;

	if( !( ent->spawnflags & DROPPED_ITEM ) )
	{
		// give them some ammo with it
		ammo_tag = ent->item->weakammo_tag;

		if( ammo_tag )
		{
			if( dmflags->integer & DF_INFINITE_AMMO )
				Add_Ammo( other, game.items[ammo_tag], 1000, qtrue );
			else
				Add_Ammo( other, game.items[ammo_tag], game.items[ammo_tag]->quantity, qtrue );
		}

		if( !( ent->spawnflags & ( DROPPED_ITEM | DROPPED_PLAYER_ITEM ) ) )
		{
			if( G_Gametype_CanRespawnItem( ent->item ) )
			{
				if( dmflags->integer & DF_WEAPONS_STAY )
					ent->flags |= FL_RESPAWN;
				else
					SetRespawn( ent, G_Gametype_RespawnTimeForItem( ent->item ) );
			}
		}
	}
	else
	{    //it's a dropped weapon
		ammo_tag = ent->item->weakammo_tag;
		if( ent->count && ammo_tag )
		{
			if( dmflags->integer & DF_INFINITE_AMMO )
				Add_Ammo( other, game.items[ammo_tag], 1000, qtrue );
			else
				Add_Ammo( other, game.items[ammo_tag], ent->count, qtrue );
		}
	}

	return qtrue;
}

//===============
//ChangeWeapon
//
//The old weapon has been dropped all the way, so make the new one
//current
//===============
void ChangeWeapon( edict_t *ent )
{
	gclient_t *client = ent->r.client;
	weapon_info_t *weaponinfo;

	//invalid
	if( client->latched_weapon < WEAP_NONE || client->latched_weapon >= WEAP_TOTAL )
		return;

	// hide laser
	if( ent->s.weapon == WEAP_LASERGUN )
		G_HideClientLaser( ent );

	ent->s.weapon = client->latched_weapon;
	client->latched_weapon = -1;

	weaponinfo = &gs_weaponInfos[ent->s.weapon];

	if( ent->s.weapon && weaponinfo->firedef->usage_count )
		client->ammo_index = weaponinfo->firedef->ammo_id;
	else
		client->ammo_index = 0;

	if( ent->s.weapon && weaponinfo->firedef_weak->usage_count )
		client->ammo_weak_index = weaponinfo->firedef_weak->ammo_id;
	else
		client->ammo_weak_index = 0;

	if( !ent->s.weapon )
		return;

	// change selected item
	client->selected_item = client->ps.stats[STAT_SELECTED_ITEM] = ent->s.weapon;

	//Send the weaponIN animation and sound style through EVENTs
	G_AddEvent( ent, EV_WEAPONUP, 1, qtrue );
}

static int SelectBestWeapon( gclient_t *client, int ignore_weapon )
{
	int weap, weap_chosen;
	weapon_info_t *weaponinfo;

	//find with strong ammos
	for( weap = WEAP_TOTAL-1; weap > WEAP_NONE; weap-- )
	{
		if( ignore_weapon > 0 && weap == ignore_weapon )
			continue;

		if( !client->inventory[weap] )
			continue;

		weaponinfo = &gs_weaponInfos[weap];
		if( !weaponinfo->firedef )
			continue;

		// wsw : pb : fixed noammo autoswitch to gunblade
		if( weaponinfo->weapon_id == WEAP_GUNBLADE )
			continue;

		if( !weaponinfo->firedef->usage_count || client->inventory[weaponinfo->firedef->ammo_id] >= weaponinfo->firedef->usage_count )
		{
			weap_chosen = weap;
			goto found;
		}
	}

	//repeat find with weak ammos
	for( weap = WEAP_TOTAL-1; weap > WEAP_NONE; weap-- )
	{
		if( ignore_weapon > 0 && weap == ignore_weapon )
			continue;

		if( !client->inventory[weap] )
			continue;

		weaponinfo = &gs_weaponInfos[weap];
		if( !weaponinfo->firedef_weak )
			continue;

		if( !weaponinfo->firedef_weak->usage_count || client->inventory[weaponinfo->firedef_weak->ammo_id] >= weaponinfo->firedef_weak->usage_count )
		{
			weap_chosen = weap;
			goto found;
		}
	}

	// didnt found any weapon so use strong gunblade
	weap_chosen = WEAP_GUNBLADE;
found:
	return weap_chosen;
}

//=================
//NoAmmoWeaponChange
//=================
static void NoAmmoWeaponChange( edict_t *ent )
{
	if( ent->r.client )
		G_AddPlayerStateEvent( ent->r.client, PSEV_NOAMMO, ent->s.weapon );
}


//================
//Use_Weapon
//
//Make the weapon ready if there is ammo
//================
void Use_Weapon( edict_t *ent, gitem_t *item )
{
	int ammocount, weakammocount;
	weapon_info_t *weaponinfo;

	//invalid weapon item
	if( item->tag < 0 || item->tag >= WEAP_TOTAL )
		return;

	// see if we're already changing to it
	if( ent->r.client->latched_weapon == item->tag )
		return;

	// see if we're already using it and not changing away from it
	if( item == game.items[ent->s.weapon] && ent->r.client->latched_weapon == -1 )
		return;

	weaponinfo = &gs_weaponInfos[item->tag];

	if( !g_select_empty->integer && !( item->type & IT_AMMO ) )
	{
		if( weaponinfo->firedef->usage_count )
		{
			if( weaponinfo->firedef->ammo_id )
				ammocount = ent->r.client->inventory[weaponinfo->firedef->ammo_id];
			else
				ammocount = weaponinfo->firedef->usage_count; // can change weapon
		}
		else
			ammocount = 1; // can change weapon

		if( weaponinfo->firedef_weak->usage_count )
		{
			if( weaponinfo->firedef_weak->ammo_id )
				weakammocount = ent->r.client->inventory[weaponinfo->firedef_weak->ammo_id];
			else
				weakammocount = weaponinfo->firedef_weak->usage_count; // can change weapon
		}
		else
			weakammocount = 1; // can change weapon

		if( !ammocount && !weakammocount )
		{
			return;
		}

		if( ammocount < weaponinfo->firedef->usage_count && weakammocount < weaponinfo->firedef_weak->usage_count )
		{
			return;
		}
	}

	// change to this weapon when down
	ent->r.client->latched_weapon = item->tag;
	ent->r.client->weaponstate.changing = qtrue;

	//if we don't have any weapon, no need to wait to be down
	if( !ent->s.weapon )
		ChangeWeapon( ent );
	else
	{
		if( ent->r.client->weaponstate.status != WEAPON_DROPPING )  // if many switches at once, don't bother adding the event twice
			G_AddEvent( ent, EV_WEAPONDOWN, 0, qfalse );
	}
}

//================
//Drop_Weapon
//================
void Drop_Weapon( edict_t *ent, gitem_t *item )
{
	int otherweapon;
	edict_t *drop;
	int ammodrop = 0;

	if( dmflags->integer & DF_WEAPONS_STAY )
		return;

	if( item->tag < 1 || item->tag >= WEAP_TOTAL )
	{
		G_PrintMsg( ent, "Can't drop unknown weapon\n" );
		return;
	}

	// see if we're already using it
	if( ( item->tag == ent->s.weapon || item->tag == ent->r.client->latched_weapon )
	   && ent->r.client->inventory[item->tag] == 1 )
	{
		if( item->tag != WEAP_GUNBLADE )
		{   // put the weapon down
			ent->r.client->weaponstate.status = WEAPON_DROPPING;
			ent->r.client->weaponstate.nexttime = 0;
			otherweapon = SelectBestWeapon( ent->r.client, item->tag );
			Use_Weapon( ent, game.items[otherweapon] );
			ChangeWeapon( ent );
		}
		else
		{
			G_PrintMsg( ent, "Can't drop current weapon\n" );
			return;
		}
	}

	// find out the amount of ammo to drop
	if( ent->r.client->inventory[item->tag] > 1 && ent->r.client->inventory[item->weakammo_tag] > 5 )
	{
		ammodrop = ent->r.client->inventory[item->weakammo_tag] / 2;
	}
	else // drop all
	{
		ammodrop = ent->r.client->inventory[item->weakammo_tag];
	}

	drop = Drop_Item( ent, item );
	if( drop )
	{
		ent->r.client->inventory[item->weakammo_tag] -= ammodrop;
		drop->count = ammodrop;
		drop->spawnflags |= DROPPED_PLAYER_ITEM;
		ent->r.client->inventory[item->tag]--;
	}
}


static qboolean Check_BladeAttack( edict_t *ent, int timeDelta );

//=================
//Weapon_PowerFracToAmmoUsage
//Find the amount of ammo we will consume
//=================
static int Weapon_PowerFracToAmmoUsage( gclient_t *client, firedef_t *firedef )
{
	if( !firedef || !firedef->usage_count || !firedef->ammo_id )
		return 0;

	return firedef->usage_count;
}

firedef_t *Player_GetCurrentWeaponFiredef( edict_t *ent )
{
	firedef_t *firedef = NULL;

	if( ent->deadflag )
		return NULL;

	if( ent->s.weapon < 0 || ent->s.weapon >= WEAP_TOTAL )  //invalid weapon
		return NULL;

	//find out our current fire mode
	if( ent->r.client->inventory[gs_weaponInfos[ent->s.weapon].firedef->ammo_id] >=
	   Weapon_PowerFracToAmmoUsage( ent->r.client, gs_weaponInfos[ent->s.weapon].firedef ) )
		firedef = gs_weaponInfos[ent->s.weapon].firedef;
	else
		firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;

	return firedef;
}


//======================================================================
//
//WEAPONS
//
//======================================================================



//======================================================================
//
//GUNBLADE
//
//======================================================================


//===============
//Check_BladeAttack
//===============
qboolean Check_BladeAttack( edict_t *ent, int timeDelta )
{
	edict_t	*other = NULL;
	vec3_t start, forward, offset, right, end;
	trace_t	tr;
	gclient_t *client = ent->r.client;
	firedef_t *firedef;
	int damage;
	int range;
	int knockback;

	// wsw: pb disable collision in race mode
	if( game.gametype == GAMETYPE_RACE || match.state == MATCH_STATE_COUNTDOWN )
		return qfalse;

	if( game.gametype == GAMETYPE_CA && match.state != MATCH_STATE_WARMUP && match.roundstate != MATCH_STATE_PLAYTIME )  // FIXME: does this code keep consistency?
		return qfalse;

#ifdef DUEL_ARENA
	if( game.gametype == GAMETYPE_DA && match.state != MATCH_STATE_WARMUP && match.roundstate != MATCH_STATE_PLAYTIME )  // FIXME: does this code keep consistency?
		return qfalse;
#endif

	if( ent->s.weapon != WEAP_GUNBLADE || client->weaponstate.status != WEAPON_READY )
		return qfalse;

	firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	damage = firedef->damage;
	range = firedef->timeout;
	knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	VectorSet( offset, 0, 0, ent->viewheight );
	G_ProjectSource( ent->s.origin, offset, forward, right, start );

	VectorMA( start, range, forward, end );

	G_Trace4D( &tr, start, NULL, NULL, end, ent, MASK_SHOT, timeDelta );
	if( tr.fraction < 1.0 )
	{
		other = &game.edicts[tr.ent];
		if( other->r.client )
			if( !G_IsTeamDamage( other, ent ) )
			{
				client->weaponstate.status = WEAPON_RELOADING;
				client->weaponstate.nexttime += firedef->reload_time;

				client->resp.accuracy_shots[AMMO_WEAK_GUNBLADE-AMMO_CELLS]++;
				G_AddEvent( ent, EV_MUZZLEFLASH, FIRE_MODE_WEAK, qtrue );
				W_Fire_Blade( ent, range, start, forward, damage, knockback, MOD_GUNBLADE_W, timeDelta );
				return qtrue;
			}
	}
	return qfalse;
}


//=================
//Weapon_Fire_Gunblade_Weak
//=================
static void Weapon_Fire_Gunblade_Weak( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Blade( ent, firedef->timeout, start, forward, damage, knockback, MOD_GUNBLADE_W, timeDelta );
}

//=================
//Weapon_Fire_Gunblade_Strong
//=================
static void Weapon_Fire_Gunblade_Strong( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	vec3_t start, forward, right;
	float power;
	int damage;
	int knockback;
	int radius;
	int speed = firedef->speed;

	power = (float)ent->r.client->inventory[firedef->ammo_id] /(float)firedef->ammo_max;

	damage = firedef->damage * power;
	knockback = firedef->knockback * power;
	radius = firedef->splash_radius * power;
	if( damage < firedef->splash_min_damage )
		damage = firedef->splash_min_damage;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	if( ent->waterlevel == 3 )
	{
		damage = (int)( damage * 0.8f );
		knockback = (int)( knockback * 0.6f );
		speed = (int)( speed * 0.6f );
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_GunbladeBlast( ent, start, forward,
	                      damage,
	                      knockback,
	                      firedef->splash_min_damage,
	                      radius,
	                      speed,
	                      firedef->timeout,
	                      MOD_GUNBLADE_S,
	                      timeDelta );

	// ammo usage: always wastes all it's power
	if( firedef->ammo_id && !( dmflags->integer & DF_INFINITE_AMMO ) )
	{
		client->inventory[firedef->ammo_id] = min( firedef->ammo_pickup * 2, firedef->ammo_max );
		ent->r.client->gunblade_charge_waitingtime = 0;
	}
}


//======================================================================
//
//SHOCKWAVE
//
//======================================================================

static void Weapon_Fire_Shockwave_Strong( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	gclient_t *client = ent->r.client;

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Shockwave( ent, start, forward, firedef->speed, firedef->splash_radius, firedef->timeout, timeDelta );
	// ammo usage
	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
	{
		client->inventory[firedef->ammo_id] -= Weapon_PowerFracToAmmoUsage( client, firedef );
	}
}

static void Weapon_Fire_Shockwave_Weak( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	gclient_t *client = ent->r.client;

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Shockwave( ent, start, forward, firedef->speed, firedef->splash_radius, firedef->timeout, timeDelta );
	// ammo usage
	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
	{
		client->inventory[firedef->ammo_id] -= Weapon_PowerFracToAmmoUsage( client, firedef );
	}
}


//======================================================================
//
//RIOTGUN
//
//======================================================================

//=================
//Weapon_Fire_Riotgun_Strong
//=================
static void Weapon_Fire_Riotgun_Strong( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	if( ent->waterlevel == 3 )
		damage = (int)( damage * 0.8 );

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Riotgun( ent, start, forward, damage, knockback, firedef->spread,
	                firedef->projectile_count, DAMAGE_SHELL, MOD_RIOTGUN_S, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		ent->r.client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//=================
//Weapon_Fire_Riotgun_Weak
//=================
static void Weapon_Fire_Riotgun_Weak( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	if( ent->waterlevel == 3 )
		damage = (int)( damage * 0.8 );

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Riotgun( ent, start, forward, damage, knockback,	firedef->spread,
	                firedef->projectile_count, DAMAGE_SHELL, MOD_RIOTGUN_W, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		ent->r.client->inventory[firedef->ammo_id] -= firedef->usage_count;
}


//======================================================================
//
//GRENADELAUNCHER
//
//======================================================================

//=================
//Weapon_Fire_GrenadeLauncher_Strong
//=================
static void Weapon_Fire_GrenadeLauncher_Strong( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int damage = firedef->damage;
	int mindmg = firedef->splash_min_damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
		mindmg *= 2;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Grenade( ent, start, forward, firedef->speed,
	                damage, knockback, mindmg, firedef->splash_radius, firedef->timeout, MOD_GRENADE_S, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//=================
//Weapon_Fire_GrenadeLauncher_Weak
//=================
static void Weapon_Fire_GrenadeLauncher_Weak( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int mindmg = firedef->splash_min_damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
		mindmg *= 2;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Grenade( ent, start, forward, firedef->speed,
	                damage, knockback, mindmg, firedef->splash_radius, firedef->timeout, MOD_GRENADE_W, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}


//======================================================================
//
//ROCKETLAUNCHER
//
//======================================================================

//=================
//P_TimeDeltaPrestepProjectile
//=================
static void P_TimeDeltaPrestepProjectile( edict_t *projectile, edict_t *passent, int timeDelta )
{
	vec3_t forward, start;
	trace_t	trace;
	int i;
	VectorScale( projectile->velocity, -( timeDelta )*0.001, forward );
	VectorMA( projectile->s.origin, 1, forward, start );

	G_Trace4D( &trace, projectile->s.origin, projectile->r.mins, projectile->r.maxs, start, passent, MASK_SHOT, 0 );
	for( i = 0; i < 3; i++ )
		projectile->s.origin[i] = projectile->s.origin2[i] = projectile->olds.origin[i] = trace.endpos[i];

	GClip_LinkEntity( projectile );
	SV_Impact( projectile, &trace );

	if( projectile->r.inuse )
		projectile->waterlevel = ( G_PointContents4D( projectile->s.origin, projectile->timeDelta ) & MASK_WATER ) ? qtrue : qfalse;
}

//=================
//Weapon_Fire_RocketLauncher_Strong
//=================
static void Weapon_Fire_RocketLauncher_Strong( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int mindmg = firedef->splash_min_damage;
	int damage = firedef->damage;
	int speed = firedef->speed;
	int knockback = firedef->knockback;
	edict_t	*rocket;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	//slow down under water
	if( ent->waterlevel == 3 )
		speed = (int)speed*0.5;

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	rocket = W_Fire_Rocket( ent, start, forward, speed, damage, knockback, mindmg, firedef->splash_radius, firedef->timeout, MOD_ROCKET_S, 0 );
	if( rocket )
	{
		P_TimeDeltaPrestepProjectile( rocket, ent, timeDelta );
	}

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		ent->r.client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//=================
//Weapon_Fire_RocketLauncher_Weak
//=================
static void Weapon_Fire_RocketLauncher_Weak( edict_t *ent, int timeDelta )
{
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int mindmg = firedef->splash_min_damage;
	int damage = firedef->damage;
	int speed = firedef->speed;
	int knockback = firedef->knockback;
	edict_t	*rocket;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	//slow down under water
	if( ent->waterlevel == 3 )
		speed = (int)speed*0.5;

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	rocket = W_Fire_Rocket( ent, start, forward, speed, damage, knockback, mindmg, firedef->splash_radius, firedef->timeout, MOD_ROCKET_W, 0 );
	if( rocket )
	{
		P_TimeDeltaPrestepProjectile( rocket, ent, timeDelta );
	}

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		ent->r.client->inventory[firedef->ammo_id] -= firedef->usage_count;
}


//======================================================================
//
//PLASMAGUN
//
//======================================================================

//=================
//Weapon_Fire_Plasmagun_Strong
//=================
static void Weapon_Fire_Plasmagun_Strong( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int damage = firedef->damage;
	int mindmg = firedef->splash_min_damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
		mindmg *= 2;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Plasma( ent, start, forward, damage, knockback, mindmg, firedef->splash_radius,
	               firedef->speed, firedef->timeout, MOD_PLASMA_S, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//=================
//Weapon_Fire_Plasmagun_Weak
//=================
static void Weapon_Fire_Plasmagun_Weak( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int mindmg = firedef->splash_min_damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
		mindmg *= 2;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Plasma( ent, start, forward, damage, knockback, mindmg, firedef->splash_radius,
	               firedef->speed, firedef->timeout, MOD_PLASMA_W, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}


//======================================================================
//
//LASERGUN
//
//======================================================================

//=================
//Weapon_Fire_Lasergun_Strong
//=================
static void Weapon_Fire_Lasergun_Strong( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Lasergun( ent, start, forward, damage, knockback, firedef->timeout, DAMAGE_ENERGY, MOD_LASERGUN_S, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//=================
//Weapon_Fire_Lasergun_Weak
//=================
static void Weapon_Fire_Lasergun_Weak( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	// find the endpoint into the ones in the backup trail
	{
		vec3_t end;
		unsigned int backtime = 25;
		unsigned int framenum = level.framenum - 1;

		// FIXME: Vic: this loop doesn't make any sense to me, but ok..
		while( level.framenum >= framenum + LASERGUN_WEAK_TRAIL_MASK )
		{
			if( level.time >= backtime + client->lasergunTrailTimes[framenum & LASERGUN_WEAK_TRAIL_MASK] )
			{
				break;
			}
			framenum--;
		}

		VectorCopy( client->lasergunTrail[framenum & LASERGUN_WEAK_TRAIL_MASK], end );
		W_Fire_Lasergun_Weak( ent, start, end, forward, damage, knockback, firedef->timeout, DAMAGE_ENERGY,
		                      MOD_LASERGUN_W, timeDelta );
	}

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}


//======================================================================
//
//ELECTROBOLT
//
//======================================================================

//=================
//Weapon_Fire_Electrobolt_Strong
//=================
static void Weapon_Fire_Electrobolt_Strong( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int damage = firedef->damage;
	int knockback = firedef->knockback;
	int dmgflags = DAMAGE_ENERGY;
	int range = firedef->timeout;

	if( g_instagib->integer )
	{
		range = 10000;
		damage = 200;
		dmgflags |= DAMAGE_NO_ARMOR;
	}
	else if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Electrobolt_Strong( ent, start, forward, firedef->speed, damage, knockback, range, dmgflags, MOD_ELECTROBOLT_S, timeDelta );

	if( !g_instagib->integer )
	{
		if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
			client->inventory[firedef->ammo_id] -= firedef->usage_count;
	}
}

//=================
//Weapon_Fire_Electrobolt_Weak
//=================
static void Weapon_Fire_Electrobolt_Weak( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Electrobolt_Weak( ent, start, forward, firedef->speed, damage, knockback, firedef->timeout, DAMAGE_BOLT_WEAK, MOD_ELECTROBOLT_W, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//======================================================================
//
//INSTAGUN
//
//======================================================================

//=================
//Weapon_Fire_Instagun_Strong
//=================
static void Weapon_Fire_Instagun_Strong( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef;
	int damage = firedef->damage;
	int knockback = firedef->knockback;
	int dmgflags = DAMAGE_ENERGY;
	int range = firedef->timeout;

	if( g_instagib->integer )
	{
		range = 10000;
		damage = 200;
		dmgflags |= DAMAGE_NO_ARMOR;
	}
	else if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Instagun_Strong( ent, start, forward, firedef->speed, damage, knockback, firedef->splash_radius, range, dmgflags, MOD_INSTAGUN_S, timeDelta );

	if( !g_instagib->integer )
	{
		if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
			client->inventory[firedef->ammo_id] -= firedef->usage_count;
	}
}

//=================
//Weapon_Fire_Instagun_Weak
//=================
static void Weapon_Fire_Instagun_Weak( edict_t *ent, int timeDelta )
{
	gclient_t *client = ent->r.client;
	vec3_t start, forward, right;
	firedef_t *firedef = gs_weaponInfos[ent->s.weapon].firedef_weak;
	int damage = firedef->damage;
	int knockback = firedef->knockback;

	if( is_quad )
	{
		damage *= QUAD_DAMAGE_SCALE;
		knockback *= QUAD_KNOCKBACK_SCALE;
	}

	AngleVectors( ent->r.client->ps.viewangles, forward, right, NULL );
	G_AddEvent( ent, EV_MUZZLEFLASH, firedef->fire_mode, qtrue );
	G_ProjectSource( ent->s.origin, tv( 0, 0, ent->viewheight ), forward, right, start );

	W_Fire_Instagun_Weak( ent, start, forward, firedef->speed, damage, knockback, firedef->timeout, DAMAGE_NO_ARMOR, MOD_INSTAGUN_W, timeDelta );

	if( firedef->ammo_id && firedef->usage_count && !( dmflags->integer & DF_INFINITE_AMMO ) )
		client->inventory[firedef->ammo_id] -= firedef->usage_count;
}

//==================
//Weapon_Fire
//==================
static qboolean Weapon_Fire( firedef_t *firedef, edict_t *ent, int timeDelta )
{
	if( firedef->fire_mode == FIRE_MODE_STRONG )
	{
		switch( firedef->weapon_id )
		{
		case WEAP_GUNBLADE:
			Weapon_Fire_Gunblade_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_SHOCKWAVE:
			Weapon_Fire_Shockwave_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_RIOTGUN:
			Weapon_Fire_Riotgun_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_GRENADELAUNCHER:
			Weapon_Fire_GrenadeLauncher_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_ROCKETLAUNCHER:
			Weapon_Fire_RocketLauncher_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_PLASMAGUN:
			Weapon_Fire_Plasmagun_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_ELECTROBOLT:
			Weapon_Fire_Electrobolt_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_LASERGUN:
			Weapon_Fire_Lasergun_Strong( ent, timeDelta );
			return qtrue;
		case WEAP_INSTAGUN:
			Weapon_Fire_Instagun_Strong( ent, timeDelta );
			return qtrue;
		default:
			return qfalse;
		}
	}
	else
	{
		switch( firedef->weapon_id )
		{
		case WEAP_GUNBLADE:
			Weapon_Fire_Gunblade_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_SHOCKWAVE:
			Weapon_Fire_Shockwave_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_RIOTGUN:
			Weapon_Fire_Riotgun_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_GRENADELAUNCHER:
			Weapon_Fire_GrenadeLauncher_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_ROCKETLAUNCHER:
			Weapon_Fire_RocketLauncher_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_PLASMAGUN:
			Weapon_Fire_Plasmagun_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_ELECTROBOLT:
			Weapon_Fire_Electrobolt_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_LASERGUN:
			Weapon_Fire_Lasergun_Weak( ent, timeDelta );
			return qtrue;
		case WEAP_INSTAGUN:
			Weapon_Fire_Instagun_Weak( ent, timeDelta );
			return qtrue;
		default:
			return qfalse;
		}
	}

}

//=================
//Weapon_GenericFrametime
//=================
static void Weapon_GenericFrametime( edict_t *ent, int msec, int timeDelta, firedef_t *firedef )
{
	int actions;
	gclient_t *client = ent->r.client;

	if( ent->deadflag )
		return;

	if( client->ps.pmove.stats[PM_STAT_SLOW] > 0 )
		client->weaponstate.nexttime += (int)game.frametime/5.0*4.0; // pretty hax

	actions = GS_Weaponstate_Run( &client->weaponstate, msec, firedef,
	                             ( client->buttons & BUTTON_ATTACK ) );

	// hide laser if we are not shooting it anymore
	if( ent->s.weapon == WEAP_LASERGUN && !( actions & WEAPON_ACTION_STATE_CHANGE ) &&
	    client->weaponstate.status != WEAPON_RELOADING )
	{
		G_HideClientLaser( ent );
	}

	//weapdown animation
	if( ( actions & WEAPON_ACTION_STATE_CHANGE ) && client->weaponstate.status == WEAPON_DROPPING )
	{
	}

	if( actions & WEAPON_ACTION_WEAPON_CHANGE )
		ChangeWeapon( ent );

	if( actions & WEAPON_ACTION_FIRE )
	{
		if( match.state == MATCH_STATE_COUNTDOWN )  // FIXME: does this code keep consistency?
			return;
		if( game.gametype == GAMETYPE_CA && match.state != MATCH_STATE_WARMUP && match.roundstate != MATCH_STATE_PLAYTIME )  // FIXME: does this code keep consistency?
			return;
#ifdef DUEL_ARENA
		if( game.gametype == GAMETYPE_DA && match.state != MATCH_STATE_WARMUP && match.roundstate != MATCH_STATE_PLAYTIME )  // FIXME: does this code keep consistency?
			return;
#endif
		if( firedef->ammo_id && ( !firedef->usage_count ||
		                         client->inventory[firedef->ammo_id] >= Weapon_PowerFracToAmmoUsage( client, firedef ) ) )
		{
			if( client->quad_timeout > level.time && ent->s.weapon != WEAP_LASERGUN )
				G_Sound( ent, CHAN_AUTO, trap_SoundIndex( S_QUAD_FIRE ), 1, ATTN_NORM );

			if( Weapon_Fire( firedef, ent, timeDelta ) )
				client->resp.accuracy_shots[firedef->ammo_id-AMMO_CELLS] += firedef->projectile_count;
		}
		else
		{
			NoAmmoWeaponChange( ent );
		}
	}
}

//=================
//Weapon_Generic
// Generic think for all weapons. It chooses the weapon firedef and calls the states machine
//=================
static void Weapon_Generic( edict_t *ent, int msec, int timeDelta )
{
	if( ent->deadflag )
		return;

	if( ent->s.weapon < 0 || ent->s.weapon >= WEAP_TOTAL )  //invalid weapon
		return;

	if( Check_BladeAttack( ent, timeDelta ) )
		return;

	Weapon_GenericFrametime( ent, msec, timeDelta, Player_GetCurrentWeaponFiredef( ent ) );
}

//=================
//Think_Weapon
//
//=================
void Think_Weapon( edict_t *ent, int msec )
{
	if( match.state > MATCH_STATE_PLAYTIME )
		return;

	if( ent->s.weapon > 0 && ( game.items[ent->s.weapon]->type & IT_WEAPON ) )
	{
		is_quad = ( ent->r.client->quad_timeout > level.time );
		Weapon_Generic( ent, msec, ent->r.client->timeDelta );
	}
}
