/*
   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.

 */
#include "g_local.h"

//==================
//W_Fire_Lead
//the seed is important to be as pointer for cgame prediction accuracy
//==================
static void W_Fire_Lead( edict_t *self, vec3_t start, vec3_t aimdir, vec3_t axis[3], int damage, int kick, int hspread, int vspread, int *seed, int dflags, int mod, int timeDelta )
{
	trace_t	tr;
	vec3_t dir;
	vec3_t end;
	float r;
	float u;
	vec3_t water_start;
	int content_mask = MASK_SHOT | MASK_WATER;

	G_Trace4D( &tr, self->s.origin, NULL, NULL, start, self, MASK_SHOT, timeDelta );
	if( !( tr.fraction < 1.0 ) )
	{
#ifdef SPIRALRIOT
		// Uz: fixed pattern: spiral
		r = cos( ( *seed ) )*hspread * ( *seed );
		u = sin( ( *seed ) )*vspread * ( *seed );

#else
#if 1
		// circle
		double alpha = M_PI *Q_crandom( seed ); // [-PI ..+PI]
		double s = fabs( Q_crandom( seed ) ); // [0..1]
		r = s *cos( alpha )*hspread;
		u = s *sin( alpha )*vspread;
#else
		// square
		r = Q_crandom( seed ) * hspread;
		u = Q_crandom( seed ) * vspread;
#endif
#endif
		VectorMA( start, 8192, axis[0], end );
		VectorMA( end, r, axis[1], end );
		VectorMA( end, u, axis[2], end );

		if( G_PointContents4D( start, timeDelta ) & MASK_WATER )
		{
			VectorCopy( start, water_start );
			content_mask &= ~MASK_WATER;
		}

		G_Trace4D( &tr, start, NULL, NULL, end, self, content_mask, timeDelta );

		// see if we hit water
		if( tr.contents & MASK_WATER )
		{
			VectorCopy( tr.endpos, water_start );

			if( !VectorCompare( start, tr.endpos ) )
			{
				vec3_t forward, right, up;

				// change bullet's course when it enters water
				VectorSubtract( end, start, dir );
				VecToAngles( dir, dir );
				AngleVectors( dir, forward, right, up );
#ifdef SPIRALRIOT
				// Uz: fixed pattern: spiral
				r = cos( ( *seed ) )*hspread * 1.5 *( *seed );
				u = sin( ( *seed ) )*vspread * 1.5 * ( *seed );
#else
#if 1
				// circle
				alpha = M_PI *Q_crandom( seed ); // [-PI ..+PI]
				s = fabs( Q_crandom( seed ) ); // [0..1]
				r = s *cos( alpha )*hspread*1.5;
				u = s *sin( alpha )*vspread*1.5;
#else
				r = Q_crandom( seed ) * hspread * 2;
				u = Q_crandom( seed ) * vspread * 2;
#endif
#endif
				VectorMA( water_start, 8192, forward, end );
				VectorMA( end, r, right, end );
				VectorMA( end, u, up, end );
			}

			// re-trace ignoring water this time
			G_Trace4D( &tr, water_start, NULL, NULL, end, self, MASK_SHOT, timeDelta );
		}
	}

	// send gun puff / flash
	if( tr.fraction < 1.0 && tr.ent != -1 )
	{
		if( game.edicts[tr.ent].takedamage )
		{
			T_Damage( &game.edicts[tr.ent], self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, dflags, mod );
		}
		else
		{
			if( !( tr.surfFlags & SURF_NOIMPACT ) )
			{
			}
		}
	}
}

//==================
//W_Touch_Projectile - Generic projectile touch func. Only for replacement in tests
//==================
static void W_Touch_Projectile( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	vec3_t dir;
	int radius;

	//don't hurt onwner for the 1st second?
	if( other == ent->r.owner )
	{
		if( !g_projectile_touch_owner->integer ||
		   ( g_projectile_touch_owner->integer && ent->timeStamp + 1000 > level.time ) )
			return;
	}

	if( surfFlags & SURF_NOIMPACT )
	{
		G_FreeEdict( ent );
		return;
	}

	if( other->takedamage )
	{
		VectorSubtract( other->s.origin, ent->s.origin, dir );
		VectorNormalize( dir );
		T_Damage( other, ent, ent->r.owner, dir, ent->s.origin, plane->normal, ent->dmg, ent->dmg, 0, MOD_EXPLOSIVE );
	}
	T_RadiusDamage( ent, ent->r.owner, plane, ent->dmg, ent->dmg_knockback, ent->radius_dmg, other, ent->dmg_radius, MOD_EXPLOSIVE );

	// turn entity into event
	radius = ( ( ent->dmg_radius*1/8 ) > 255 ) ? 255 : ( ent->dmg_radius*1/8 );
	VectorMA( ent->s.origin, -0.02, ent->velocity, ent->s.origin );
	G_TurnEntityIntoEvent( ent, EV_EXPLOSION1, DirToByte( plane ? plane->normal : NULL ) );
	ent->s.firemode = FIRE_MODE_STRONG;
	ent->s.weapon = radius;
}

//==================
//W_ProjectilePrestep
//offset the projectile in front of the player by a fixed distance
//==================
void W_ProjectilePrestep( edict_t *ent )
{
	vec3_t dir, dest;
	int mask, i;
	trace_t	trace;

	VectorNormalize2( ent->velocity, dir );
	mask = ( ent->r.clipmask ) ? ent->r.clipmask : MASK_SHOT; // race trick should come set up inside clipmask

	if( ent->s.linearProjectile )
	{
		VectorMA( ent->s.origin2, g_projectile_prestep->value, dir, dest );
		G_Trace4D( &trace, ent->s.origin2, ent->r.mins, ent->r.maxs, dest, ent->r.owner, mask, ent->timeDelta );
		VectorCopy( ent->velocity, ent->s.linearProjectileVelocity );
	}
	else
	{
		VectorMA( ent->s.origin, g_projectile_prestep->value, dir, dest );
		G_Trace4D( &trace, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->r.owner, mask, ent->timeDelta );
		VectorClear( ent->s.linearProjectileVelocity );
	}

	for( i = 0; i < 3; i++ )
		ent->s.origin[i] = ent->s.origin2[i] = ent->olds.origin[i] = trace.endpos[i];

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

	// set initial water state
	if( ent->r.inuse )
		ent->waterlevel = ( G_PointContents4D( ent->s.origin, ent->timeDelta ) & MASK_WATER ) ? qtrue : qfalse;
}

//==================
//W_Fire_LinearProjectile - Spawn a generic linear projectile without a model, touch func, sound nor mod
//==================
static edict_t *W_Fire_LinearProjectile( edict_t *self, vec3_t start, vec3_t dir, int speed,
                                         int damage, int knockback, int radius_damage, int radius, int timeout, int timeDelta )
{
	edict_t	*projectile;

	VectorNormalize( dir );

	projectile = G_Spawn();
	VectorCopy( start, projectile->s.origin );
	VectorCopy( start, projectile->s.old_origin );
	VectorCopy( start, projectile->olds.origin );

	VecToAngles( dir, projectile->s.angles );
	VectorScale( dir, speed, projectile->velocity );
	GS_SnapVelocity( projectile->velocity );

	projectile->movetype = MOVETYPE_LINEARPROJECTILE;
	projectile->s.linearProjectile = qtrue;

	projectile->r.solid = SOLID_BBOX;
	projectile->r.clipmask = ( game.gametype != GAMETYPE_RACE ) ? MASK_SHOT : MASK_SOLID;

	projectile->s.renderfx = 0;
	projectile->r.svflags = SVF_PROJECTILE;
	// enable me when drawing exception is added to cgame
	projectile->r.svflags |= SVF_TRANSMITORIGIN2;
	VectorClear( projectile->r.mins );
	VectorClear( projectile->r.maxs );
	projectile->s.modelindex = 0;
	projectile->r.owner = self;
	projectile->touch = W_Touch_Projectile; //generic one. Should be replaced after calling this func
	projectile->nextthink = level.time + timeout;
	projectile->think = G_FreeEdict;
	projectile->dmg = damage;
	projectile->dmg_knockback = knockback;
	projectile->radius_dmg = radius_damage;
	projectile->dmg_radius = radius;
	projectile->classname = NULL; // should be replaced after calling this func.
	projectile->style = 0;
	projectile->s.sound = 0;
	projectile->timeStamp = level.time;
	projectile->s.linearProjectileTimeStamp = game.serverTime;
	projectile->timeDelta = timeDelta;

	GClip_LinkEntity( projectile );

	// update some data required for the transmission
	VectorCopy( projectile->velocity, projectile->s.linearProjectileVelocity );
	projectile->s.team = self->s.team;
	projectile->s.modelindex2 = ( abs( timeDelta ) > 255 ) ? 255 : (unsigned int)abs( timeDelta );
	return projectile;
}

//==================
//W_Fire_TossProjectile - Spawn a generic projectile without a model, touch func, sound nor mod
//==================
static edict_t *W_Fire_TossProjectile( edict_t *self, vec3_t start, vec3_t dir, int speed,
                                       int damage, int knockback, int radius_damage, int radius, int timeout, int timeDelta )
{
	edict_t	*projectile;

	VectorNormalize( dir );

	projectile = G_Spawn();
	VectorCopy( start, projectile->s.origin );
	VectorCopy( start, projectile->s.old_origin );
	VectorCopy( start, projectile->olds.origin );

	VecToAngles( dir, projectile->s.angles );
	VectorScale( dir, speed, projectile->velocity );
	GS_SnapVelocity( projectile->velocity );
	projectile->movetype = MOVETYPE_BOUNCEGRENADE;

	// wsw: make missile fly through players in race
	if( game.gametype == GAMETYPE_RACE )
		projectile->r.clipmask = MASK_SOLID;
	else
		projectile->r.clipmask = MASK_SHOT;

	projectile->r.solid = SOLID_BBOX;
	projectile->s.renderfx = 0;
	projectile->r.svflags = SVF_PROJECTILE;
	VectorClear( projectile->r.mins );
	VectorClear( projectile->r.maxs );
	//projectile->s.modelindex = trap_ModelIndex ("models/objects/projectile/plasmagun/proj_plasmagun2.md3");
	projectile->s.modelindex = 0;
	projectile->r.owner = self;
	projectile->touch = W_Touch_Projectile; //generic one. Should be replaced after calling this func
	projectile->nextthink = level.time + timeout;
	projectile->think = G_FreeEdict;
	projectile->dmg = damage;
	projectile->dmg_knockback = knockback;
	projectile->radius_dmg = radius_damage;
	projectile->dmg_radius = radius;
	projectile->classname = NULL; // should be replaced after calling this func.
	projectile->style = 0;
	projectile->s.sound = 0;
	projectile->timeStamp = level.time;
	projectile->timeDelta = timeDelta;
	projectile->s.team = self->s.team;

	GClip_LinkEntity( projectile );

	return projectile;
}


//	------------ the actual weapons --------------

//==================
//W_Touch_Generic
// Generic projectile touch function, that should be called once before projectile is removed
// Returns false if normal function shouldn't continue
//==================
static qboolean W_Touch_Generic( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	// don't hurt owner for the first second
	if( other == ent->r.owner )
	{
		if( !g_projectile_touch_owner->integer ||
		   ( g_projectile_touch_owner->integer && ent->timeStamp + 1000 > level.time ) )
			return qfalse;
	}

	if( other->takedamage && !G_IsTeamDamage( ent->r.owner, other )
	    && other != ent->r.owner && G_ModToAmmo( ent->style ) != AMMO_NONE )
	{
		ent->r.owner->r.client->resp.accuracy_hits_direct[G_ModToAmmo( ent->style )-AMMO_CELLS]++;
	}

	return qtrue;
}

//==================
//W_Fire_Blade
//==================
void W_Fire_Blade( edict_t *self, int range, vec3_t start, vec3_t aimdir, int damage, int knockback, int mod, int timeDelta )
{
	edict_t *event, *other = NULL;
	vec3_t end;
	trace_t	trace;
	int mask = MASK_SHOT;

	VectorMA( start, range, aimdir, end );

	if( game.gametype == GAMETYPE_RACE )
		mask = MASK_SOLID;

	G_Trace4D( &trace, start, NULL, NULL, end, self, MASK_SHOT, timeDelta );
	if( trace.ent == -1 )  //didn't touch anything
		return;

	// find out what touched
	other = &game.edicts[trace.ent];
	if( !other->takedamage ) // it was the world
	{
		// wall impact
		VectorMA( trace.endpos, -0.02, aimdir, end );
		event = G_SpawnEvent( EV_BLADE_IMPACT, 0, end );
		event->s.ownerNum = self - game.edicts;
		VectorCopy( trace.plane.normal, event->s.origin2 );
		event->r.svflags = SVF_TRANSMITORIGIN2;
		return;
	}

	// it was a player
	T_Damage( other, self, self, aimdir, other->s.origin, vec3_origin, damage, knockback, 0, mod );
}

//==================
//W_Touch_GunbladeBlast
//==================
static void W_Touch_GunbladeBlast( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	vec3_t dir;
	//	int		power;

	if( !W_Touch_Generic( ent, other, plane, surfFlags ) )
		return;

	//if (surfFlags & SURF_NOIMPACT)
	//{
	//	G_FreeEdict (ent);
	//	return;
	//}

	if( other->takedamage )
	{
		float pushFrac;

		pushFrac = G_KnockbackPushFrac4D( ent->s.origin, ENTNUM( other ), dir, ent->dmg_radius, ent->timeDelta );
		T_Damage( other, ent, ent->r.owner, dir, ent->s.origin, plane->normal, ent->dmg, ent->dmg_knockback * pushFrac, 0, ent->style );
	}
	T_RadiusDamage( ent, ent->r.owner, plane, ent->dmg, ent->dmg_knockback, ent->radius_dmg, other, ent->dmg_radius, MOD_GUNBLADE_S ); //fixme : splash mod

	// add explosion event
	if( !( surfFlags & SURF_NOIMPACT ) && !other->takedamage )
	{
		edict_t *event;

		//power = (int)(128.0f * ((float)ent->dmg / (float)g_weaponInfos[WEAP_GUNBLADE].firedef->damage) );
		//if( power > 128 ) power = 128;

		event = G_SpawnEvent( EV_GUNBLADEBLAST_IMPACT, DirToByte( plane ? plane->normal : NULL ), ent->s.origin );
		event->s.weapon = ( ( ent->dmg_radius*1/8 ) > 127 ) ? 127 : ( ent->dmg_radius*1/8 );
		event->s.skinnum = ( ( ent->dmg_knockback*1/8 ) > 255 ) ? 255 : ( ent->dmg_knockback*1/8 );
		event->r.svflags |= SVF_NOORIGIN2;
	}

	// free at next frame
	G_FreeEdict( ent );
}

//==================
//W_Fire_GunbladeBlast
//==================
edict_t *W_Fire_GunbladeBlast( edict_t *self, vec3_t start, vec3_t dir, int damage, int knockback, int radius_damage, int radius, int speed, int timeout, int mod, int timeDelta )
{
	edict_t	*blast;

	blast = W_Fire_LinearProjectile( self, start, dir, speed, damage, knockback, radius_damage, radius, timeout, timeDelta );
	blast->s.modelindex = trap_ModelIndex( PATH_GUNBLADEBLAST_STRONG_MODEL );
	blast->s.type = ET_BLASTER;
	blast->touch = W_Touch_GunbladeBlast;
	blast->classname = "gunblade_blast";
	blast->s.renderfx |= RF_FULLBRIGHT;
	blast->style = mod;

	blast->s.sound = trap_SoundIndex( S_WEAPON_PLASMAGUN_S_FLY );

	W_ProjectilePrestep( blast );

	return blast;
}

//==================
//W_Fire_Gunblade_Bullet
//==================
void W_Fire_Gunblade_Bullet( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int knockback, int mod, int timeDelta )
{
	vec3_t dir, axis[3];
	edict_t *event;
	int seed = rand() & 255;

	VecToAngles( aimdir, dir );
	AngleVectors( dir, axis[0], axis[1], axis[2] );

	// send the event
	event = G_SpawnEvent( EV_FIRE_BULLET, seed, start );
	event->s.ownerNum = self - game.edicts;
	event->r.svflags = SVF_TRANSMITORIGIN2;
	VectorScale( axis[0], 1024, event->s.origin2 ); // DirToByte is too inaccurate

	W_Fire_Lead( self, start, aimdir, axis, damage, knockback, DEFAULT_BULLET_SPREAD, DEFAULT_BULLET_SPREAD, &seed, DAMAGE_BULLET, mod, timeDelta );
}

//==================
//W_Touch_Shockwave
//==================
static void W_Touch_Shockwave( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	G_FreeEdict( ent );
}

//==================
//W_Think_Shockwave
//==================
static void W_Think_Shockwave( edict_t *wave )
{
	edict_t *ent;

	if( wave->timeout < level.time )
	{
		G_FreeEdict( wave );
		return;
	}

	wave->nextthink = level.time + 1;
	wave->think = W_Think_Shockwave;

	ent = NULL;
	while( ( ent = G_FindBoxInRadius( ent, wave->s.origin, wave->dmg_radius ) ) != NULL )
	{
		//don't hurt owner for the one second
		if( ent == wave->r.owner )
		{
			if( !g_projectile_touch_owner->integer ||
			   ( g_projectile_touch_owner->integer && wave->timeStamp + 1000 > level.time ) )
				continue;
		}

		if( !ent->takedamage || !ent->r.client )
			continue;

		ent->r.client->ps.pmove.stats[PM_STAT_SLOW] = game.frametime;
	}
}

//==================
//W_Fire_Shockwave
//==================
edict_t *W_Fire_Shockwave( edict_t *self, vec3_t start, vec3_t aimdir, float speed, int radius, int timeout, int timeDelta )
{
	edict_t	*wave;

	wave = W_Fire_LinearProjectile( self, start, aimdir, (int)speed, 0, 0, 0, radius, 0, timeDelta );

	wave->timeout = level.time + timeout;
	wave->nextthink = level.time + 1;
	wave->think = W_Think_Shockwave;

	wave->s.modelindex = trap_ModelIndex( PATH_ROCKET_EXPLOSION_MODEL );
	wave->touch = W_Touch_Shockwave;
	wave->classname = "wave";
	wave->s.renderfx |= RF_FULLBRIGHT;
	wave->s.type = ET_SHOCKWAVE;
	//wave->style = mod;

	W_ProjectilePrestep( wave );

	return wave;
}


static void W_Fire_Riotgun2( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int knockback, int spread, int count, int dflags, int mod, int timeDelta )
{
	vec3_t dir, axis[3];
	edict_t *event;
	int i, seed = rand() & 255;

	VecToAngles( aimdir, dir );
	AngleVectors( dir, axis[0], axis[1], axis[2] );

	// send the event
	event = G_SpawnEvent( EV_FIRE_RIOTGUN, seed, start );
	event->s.eventCount = count;
	event->s.ownerNum = self - game.edicts;
	event->r.svflags = SVF_TRANSMITORIGIN2;
	VectorScale( axis[0], 4096, event->s.origin2 ); // DirToByte is too inaccurate

	//send spreads inside these (the free shorts I found)
	event->s.skinnum = spread;

	for( i = 0; i < count; i++ )
#ifdef SPIRALRIOT
		W_Fire_Lead( self, start, aimdir, axis, damage, knockback, spread, spread, &i, dflags, mod, timeDelta );
#else
		W_Fire_Lead( self, start, aimdir, axis, damage, knockback, spread, spread, &seed, dflags, mod, timeDelta );
#endif
}

void W_Fire_Riotgun( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int knockback, int spread, int count, int dflags, int mod, int timeDelta )
{
	W_Fire_Riotgun2( self, start, aimdir, damage, knockback, spread, count, dflags, mod, timeDelta );
}

//==================
//W_Grenade_ExplodeDir
//==================
static void W_Grenade_ExplodeDir( edict_t *ent, vec3_t normal )
{
	vec3_t origin;
	int radius;
	edict_t	*event;
	vec3_t up = { 0, 0, 1 };
	vec_t *dir = normal ? normal : up;

	//FIXME: if we are onground then raise our Z just a bit since we are a point?
	if( ent->enemy )
	{
		vec3_t dir;
		float pushFrac;

		pushFrac = G_KnockbackPushFrac4D( ent->s.origin, ENTNUM( ent->enemy ), dir, ent->dmg_radius, ent->timeDelta );
		T_Damage( ent->enemy, ent, ent->r.owner, dir, ent->s.origin, vec3_origin, ent->dmg, ent->dmg_knockback * pushFrac, 0, ent->style );
	}

	T_RadiusDamage( ent,
		ent->r.owner,
		NULL,
		ent->dmg,
		ent->dmg_knockback,
		ent->radius_dmg,
		ent->enemy,
		ent->dmg_radius,
		( ent->style == MOD_GRENADE_S ) ? MOD_GRENADE_SPLASH_S : MOD_GRENADE_SPLASH_W
		);

	radius = ( ( ent->dmg_radius*1/8 ) > 127 ) ? 127 : ( ent->dmg_radius*1/8 );
	VectorMA( ent->s.origin, -0.02, ent->velocity, origin );
	event = G_SpawnEvent( EV_GRENADE_EXPLOSION, ( dir ? DirToByte( dir ) : 0 ), ent->s.origin );
	event->s.firemode = ( ent->s.effects & EF_STRONG_WEAPON ) ? FIRE_MODE_STRONG : FIRE_MODE_WEAK;
	event->s.weapon = radius;
	event->r.svflags |= SVF_NOORIGIN2;

	G_FreeEdict( ent );
}

//==================
//W_Grenade_Explode
//==================
static void W_Grenade_Explode( edict_t *ent )
{
	W_Grenade_ExplodeDir( ent, NULL );
}

//==================
//W_Touch_Grenade
//==================
static void W_Touch_Grenade( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	if( !W_Touch_Generic( ent, other, plane, surfFlags ) )
		return;

	if( surfFlags & SURF_NOIMPACT )
	{
		G_FreeEdict( ent );
		return;
	}

	if( !other->takedamage )
	{
		if( ent->s.effects & EF_STRONG_WEAPON )
			ent->health -= 1;

		if( !( ent->s.effects & EF_STRONG_WEAPON ) 
			|| ( ( VectorLength( ent->velocity ) && Q_rint( ent->health ) > 0 ) || ent->timeStamp + 350 > level.time ) )
		{
			// kill some velocity on each bounce
			float fric;
			static cvar_t *g_grenade_friction = NULL;

			if( !g_grenade_friction )
				g_grenade_friction = trap_Cvar_Get( "g_grenade_friction", "0.80", CVAR_DEVELOPER );

			fric = bound( 0, g_grenade_friction->value, 1 );
			VectorScale( ent->velocity, fric, ent->velocity );

			G_AddEvent( ent, EV_GRENADE_BOUNCE, ( ent->s.effects & EF_STRONG_WEAPON ) ? FIRE_MODE_STRONG : FIRE_MODE_WEAK, qtrue );
			return;
		}
	}

	ent->enemy = other;
	W_Grenade_ExplodeDir( ent, plane ? plane->normal : NULL );
}

//==================
//W_Fire_Grenade
//==================
edict_t *W_Fire_Grenade( edict_t *self, vec3_t start, vec3_t dir, int speed, int damage, int knockback, int radius_damage, float radius, int timeout, int mod, int timeDelta )
{
	edict_t	*grenade;
	vec3_t angles;
	static cvar_t *g_grenade_gravity = NULL;

	if( !g_grenade_gravity )
		g_grenade_gravity = trap_Cvar_Get( "g_grenade_gravity", "1.3", CVAR_DEVELOPER );

	VecToAngles( dir, angles );
	angles[PITCH] -= 12; // aim some degrees upwards from view dir

	// clamp to front side of the player
	angles[PITCH] += -90; // rotate to make easier the check
	while( angles[PITCH] < -360 ) angles[PITCH] += 360;
	clamp( angles[PITCH], -180, 0 );
	angles[PITCH] += 90;
	while( angles[PITCH] > 360 ) angles[PITCH] -= 360;
	AngleVectors( angles, dir, NULL, NULL );

	grenade = W_Fire_TossProjectile( self, start, dir, speed, damage, knockback, radius_damage, radius, timeout, timeDelta );
	//VectorSet( grenade->avelocity, 300, 300, brandom(0, 600) );
	VectorClear( grenade->s.angles );
	grenade->style = mod;
	grenade->s.type = ET_GRENADE;
	grenade->movetype = MOVETYPE_BOUNCEGRENADE;
	grenade->s.renderfx |= RF_FULLBRIGHT;
	grenade->touch = W_Touch_Grenade;
	grenade->use = NULL;
	grenade->think = W_Grenade_Explode;
	grenade->classname = "grenade";
	grenade->gravity = g_grenade_gravity->value;

	if( mod == MOD_GRENADE_S )
	{
		grenade->s.modelindex = trap_ModelIndex( PATH_GRENADE_STRONG_MODEL );
		grenade->s.effects |= EF_STRONG_WEAPON;
		grenade->health = 2;
	}
	else
	{
		grenade->s.modelindex = trap_ModelIndex( PATH_GRENADE_WEAK_MODEL );
		grenade->s.effects &= ~EF_STRONG_WEAPON;
	}

	W_ProjectilePrestep( grenade );

	GClip_LinkEntity( grenade );

	return grenade;
}

//==================
//W_Touch_Rocket
//==================
static void W_Touch_Rocket( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	int mod_splash, fire_mode;
	vec3_t dir;
	float dmg_radius;

	if( !W_Touch_Generic( ent, other, plane, surfFlags ) )
		return;

	if( ent->style == MOD_ROCKET_S )
	{
		fire_mode = FIRE_MODE_STRONG;
		mod_splash = MOD_ROCKET_SPLASH_S;
	}
	else
	{
		fire_mode = FIRE_MODE_WEAK;
		mod_splash = MOD_ROCKET_SPLASH_W;
	}

	dmg_radius = ent->dmg_radius;

	if( other->takedamage )
	{
		float pushFrac;

		pushFrac = G_KnockbackPushFrac4D( ent->s.origin, ENTNUM( other ), dir, dmg_radius, ent->timeDelta );
		T_Damage( other, ent, ent->r.owner, dir, ent->s.origin, plane->normal, ent->dmg, ent->dmg_knockback * pushFrac, 0, ent->style );
	}
	T_RadiusDamage( ent, ent->r.owner, plane, ent->dmg, ent->dmg_knockback, ent->radius_dmg, other, dmg_radius, mod_splash );

	// spawn the explosion
	if( !( surfFlags & SURF_NOIMPACT ) )
	{
		edict_t *event;
		vec3_t explosion_origin;

		VectorMA( ent->s.origin, -0.02, ent->velocity, explosion_origin );
		event = G_SpawnEvent( EV_ROCKET_EXPLOSION, DirToByte( plane ? plane->normal : NULL ), explosion_origin );
		event->s.firemode = fire_mode;
		event->s.weapon = ( ( ent->dmg_radius*1/8 ) > 255 ) ? 255 : ( ent->dmg_radius*1/8 );
		event->r.svflags |= SVF_NOORIGIN2;
	}

	// free the rocket at next frame
	G_FreeEdict( ent );
}

//==================
//W_Fire_Rocket
//==================
edict_t *W_Fire_Rocket( edict_t *self, vec3_t start, vec3_t dir, int speed, int damage, int knockback, int radius_damage, int radius, int timeout, int mod, int timeDelta )
{
	edict_t	*rocket;

	rocket = W_Fire_LinearProjectile( self, start, dir, speed, damage, knockback, radius_damage, radius, timeout, timeDelta );

	rocket->s.type = ET_ROCKET; //rocket trail sfx
	if( mod == MOD_ROCKET_S )
	{
		rocket->s.modelindex = trap_ModelIndex( PATH_ROCKET_STRONG_MODEL );
		rocket->s.effects |= EF_STRONG_WEAPON;
		rocket->s.sound = trap_SoundIndex( S_WEAPON_ROCKET_S_FLY );
	}
	else
	{
		rocket->s.modelindex = trap_ModelIndex( PATH_ROCKET_WEAK_MODEL );
		rocket->s.effects &= ~EF_STRONG_WEAPON;
		rocket->s.sound = trap_SoundIndex( S_WEAPON_ROCKET_W_FLY );
	}
	rocket->touch = W_Touch_Rocket;
	rocket->think = G_FreeEdict;
	rocket->s.renderfx |= RF_FULLBRIGHT;
	rocket->classname = "rocket";
	rocket->style = mod;

	W_ProjectilePrestep( rocket );
	return rocket;
}

//==================
//W_Touch_Plasma
//==================
static void W_Touch_Plasma( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	int radius;

	if( !W_Touch_Generic( ent, other, plane, surfFlags ) )
		return;

	if( surfFlags & SURF_NOIMPACT )
	{
		G_FreeEdict( ent );
		return;
	}

	if( ent->style == MOD_PLASMA_S )
	{
		T_RadiusDamage( ent, ent->r.owner, plane, ent->dmg, ent->dmg_knockback, ent->radius_dmg, other, ent->dmg_radius, MOD_PLASMA_SPLASH_S );
		if( other->takedamage )
		{
			vec3_t dir;
			float pushFrac;

			pushFrac = G_KnockbackPushFrac4D( ent->s.origin, ENTNUM( other ), dir, ent->dmg_radius, ent->timeDelta );
			T_Damage( other, ent, ent->r.owner, dir, ent->s.origin, plane->normal, ent->dmg, ent->dmg_knockback * pushFrac, 0, MOD_PLASMA_S );
		}
		else
		{
			edict_t *event;
			radius = ( ( ent->dmg_radius*1/8 ) > 127 ) ? 127 : ( ent->dmg_radius*1/8 );
			event = G_SpawnEvent( EV_PLASMA_EXPLOSION, DirToByte( plane ? plane->normal : NULL ), ent->s.origin );
			event->s.firemode = FIRE_MODE_STRONG;
			event->s.weapon = radius & 127;
			event->r.svflags |= SVF_NOORIGIN2;
		}
	}
	else
	{
		T_RadiusDamage( ent, ent->r.owner, plane, ent->dmg, ent->dmg_knockback, ent->radius_dmg, other, ent->dmg_radius, MOD_PLASMA_SPLASH_W );
		if( other->takedamage )
		{
			vec3_t dir;
			float pushFrac;

			pushFrac = G_KnockbackPushFrac4D( ent->s.origin, ENTNUM( other ), dir, ent->dmg_radius, ent->timeDelta );
			T_Damage( other, ent, ent->r.owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, ent->dmg_knockback, 0, MOD_PLASMA_W );
		}
		else
		{
			edict_t *event;
			radius = ( ( ent->dmg_radius*1/8 ) > 127 ) ? 127 : ( ent->dmg_radius*1/8 );
			event = G_SpawnEvent( EV_PLASMA_EXPLOSION, DirToByte( plane ? plane->normal : NULL ), ent->s.origin );
			event->s.firemode = FIRE_MODE_WEAK;
			event->s.weapon = radius & 127;
			event->r.svflags |= SVF_NOORIGIN2;
		}
	}

	G_FreeEdict( ent );
}

//==================
//W_Plasma_Backtrace
//==================
static void W_Plasma_Backtrace( edict_t *ent, const vec3_t start )
{
	trace_t	tr;
	vec3_t mins, maxs, oldorigin;

	if( game.gametype == GAMETYPE_RACE )
		return;

	if( ent->style == MOD_PLASMA_S )
	{
		VectorSet( mins, -10.0f, -10.0f, -10.0f );
		VectorSet( maxs, 10.0f, 10.0f, 10.0f );
	}
	else
	{
		VectorSet( mins, -5.0f, -5.0f, -5.0f );
		VectorSet( maxs, 5.0f, 5.0f, 5.0f );
	}

	VectorCopy( ent->s.origin, oldorigin );
	VectorCopy( start, ent->s.origin );

	do
	{
		G_Trace4D( &tr, ent->s.origin, mins, maxs, oldorigin, ent, ( CONTENTS_BODY|CONTENTS_CORPSE ), ent->timeDelta );

		VectorCopy( tr.endpos, ent->s.origin );

		if( tr.ent == -1 )
		{
			break;
		}
		if( tr.allsolid || tr.startsolid )
		{
			W_Touch_Plasma( ent, &game.edicts[tr.ent], NULL, 0 );
		}
		else if( tr.fraction != 1.0 )
		{
			W_Touch_Plasma( ent, &game.edicts[tr.ent], &tr.plane, tr.surfFlags );
		}
		else
		{
			break;
		}
	}
	while( ent->r.inuse && ent->s.type == ET_PLASMA && !VectorCompare( ent->s.origin, oldorigin ) );

	if( ent->r.inuse && ent->s.type == ET_PLASMA )
		VectorCopy( oldorigin, ent->s.origin );
}

//==================
//W_Think_Plasma
//==================
static void W_Think_Plasma( edict_t *ent )
{
	vec3_t start;

	if( ent->timeout < level.time )
	{
		G_FreeEdict( ent );
		return;
	}

	if( ent->r.inuse )
		ent->nextthink = level.time + 1;

	VectorMA( ent->s.origin, -( game.frametime * 0.001 ), ent->velocity, start );

	W_Plasma_Backtrace( ent, start );
}

//==================
//W_AutoTouch_Plasma
//==================
static void W_AutoTouch_Plasma( edict_t *ent, edict_t *other, cplane_t *plane, int surfFlags )
{
	W_Think_Plasma( ent );
	if( !ent->r.inuse || ent->s.type != ET_PLASMA )
		return;

	W_Touch_Plasma( ent, other, plane, surfFlags );
}

//==================
//W_Fire_Plasma
//==================
edict_t *W_Fire_Plasma( edict_t *self, vec3_t start, vec3_t dir, int damage, int knockback, int radius_damage, int radius, int speed, int timeout, int mod, int timeDelta )
{
	edict_t	*plasma;

	plasma = W_Fire_LinearProjectile( self, start, dir, speed, damage, knockback, radius_damage, radius, timeout, timeDelta );
	plasma->s.type = ET_PLASMA;
	plasma->classname = "plasma";
	plasma->s.renderfx |= RF_FULLBRIGHT;
	plasma->style = mod;

	plasma->think = W_Think_Plasma;
	plasma->touch = W_AutoTouch_Plasma;
	plasma->nextthink = level.time + 1;
	plasma->timeout = level.time + timeout;

	if( mod == MOD_PLASMA_S )
	{
		plasma->s.modelindex = trap_ModelIndex( PATH_PLASMA_STRONG_MODEL );
		plasma->s.sound = trap_SoundIndex( S_WEAPON_PLASMAGUN_S_FLY );
		plasma->s.effects |= EF_STRONG_WEAPON;
	}
	else
	{
		plasma->s.modelindex = trap_ModelIndex( PATH_PLASMA_WEAK_MODEL );
		plasma->s.sound = trap_SoundIndex( S_WEAPON_PLASMAGUN_W_FLY );
		plasma->s.effects &= ~EF_STRONG_WEAPON;
	}

	W_ProjectilePrestep( plasma );
	if( plasma->r.inuse && plasma->s.type == ET_PLASMA )
		W_Plasma_Backtrace( plasma, start );

	return plasma;
}

//==================
//W_Touch_Bolt
//==================
static void W_Touch_Bolt( edict_t *self, edict_t *other, cplane_t *plane, int surfFlags )
{
	edict_t *event;
	qboolean missed = qtrue;

	if( !W_Touch_Generic( self, other, plane, surfFlags ) )
		return;

	if( other->takedamage )
	{
		vec3_t invdir;
		T_Damage( other, self, self->r.owner, self->velocity, self->s.origin, plane->normal, self->dmg, self->dmg_knockback, 0, MOD_ELECTROBOLT_W );
		VectorNormalize2( self->velocity, invdir );
		VectorScale( invdir, -1, invdir );
		event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( invdir ), self->s.origin );
		event->s.firemode = FIRE_MODE_WEAK;
		event->r.svflags |= SVF_NOORIGIN2;
		if( other->r.client ) missed = qfalse;
	}
	else if( !( surfFlags & SURF_NOIMPACT ) )
	{                                     // add explosion event
		event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( plane ? plane->normal : NULL ), self->s.origin );
		event->s.firemode = FIRE_MODE_WEAK;
		event->r.svflags |= SVF_NOORIGIN2;
	}

	if( missed )
		G_AwardPlayerMissedElectrobolt( self->r.owner, MOD_ELECTROBOLT_W ); // hit something that isnt a player

	G_FreeEdict( self );
}

//==================
//W_Fire_Electrobolt_Strong
//==================
void W_Fire_Electrobolt_Strong( edict_t *self, vec3_t start, vec3_t aimdir, float speed, int damage, int knockback, int range, int dflags, int mod, int timeDelta )
{
	vec3_t from;
	vec3_t end;
	trace_t	tr;
	edict_t	*ignore, *event;
	int mask;
	qboolean missed = qtrue;

	VectorMA( start, range, aimdir, end );
	VectorCopy( start, from );
	ignore = self;
	mask = MASK_SHOT;
	if( game.gametype == GAMETYPE_RACE )
		mask = MASK_SOLID;
	tr.ent = -1;
	while( ignore )
	{
		G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta );
		VectorCopy( tr.endpos, from );
		ignore = NULL;
		if( tr.ent == -1 )
			break;
		// some entity was touched
		if( tr.ent == world->s.number )  // stop dead if hit the world
			break;
		if( game.edicts[tr.ent].movetype == MOVETYPE_NONE || game.edicts[tr.ent].movetype == MOVETYPE_PUSH )
			break;

		// allow trail to go through SOLID_BBOX entities (players, gibs, etc)
		if( game.edicts[tr.ent].r.solid == SOLID_BBOX )
			ignore = &game.edicts[tr.ent];

		if( ( &game.edicts[tr.ent] != self ) && ( game.edicts[tr.ent].takedamage ) )
		{
			T_Damage( &game.edicts[tr.ent], self, self, aimdir, tr.endpos, tr.plane.normal, damage, knockback, dflags, mod );
			// spawn a impact event on each damaged ent
			event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos );
			event->s.firemode = FIRE_MODE_STRONG;
			if( game.edicts[tr.ent].r.client )
				missed = qfalse;
		}
	}

	if( missed )
		G_AwardPlayerMissedElectrobolt( self, mod );

	// send the weapon fire effect
	event = G_SpawnEvent( EV_ELECTROTRAIL, ( tr.ent != -1 ) ? qtrue : qfalse, start );
	event->r.svflags = SVF_TRANSMITORIGIN2;
	VectorCopy( from, event->s.origin2 );
	event->s.ownerNum = ENTNUM( self ); // identify who fired for cgame

	if( !g_instagib->integer && tr.ent == -1 )
	{   // didn't touch anything, not even a wall
		edict_t	*bolt;
		firedef_t *firedef = gs_weaponInfos[self->s.weapon].firedef_weak;

		// fire a weak EB from the end position
		bolt = W_Fire_LinearProjectile( self, end, aimdir, firedef->speed, damage, knockback, 0, 0, firedef->timeout, timeDelta );
		bolt->s.modelindex = trap_ModelIndex( PATH_ELECTROBOLT_WEAK_MODEL );
		bolt->s.type = ET_ELECTRO_WEAK; //add particle trail and light
		bolt->s.ownerNum = ENTNUM( self );
		bolt->touch = W_Touch_Bolt;
		bolt->classname = "bolt";
		bolt->s.renderfx |= RF_FULLBRIGHT;
		bolt->style = mod;
	}
}

//==================
//W_Fire_Electrobolt_Weak
//==================
edict_t *W_Fire_Electrobolt_Weak( edict_t *self, vec3_t start, vec3_t aimdir, float speed, int damage, int knockback, int timeout, int dflags, int mod, int timeDelta )
{
	edict_t	*bolt;

	// projectile, weak mode
	bolt = W_Fire_LinearProjectile( self, start, aimdir, speed, damage, knockback, 0, 0, timeout, timeDelta );
	bolt->s.modelindex = trap_ModelIndex( PATH_ELECTROBOLT_WEAK_MODEL );
	bolt->s.type = ET_ELECTRO_WEAK; //add particle trail and light
	bolt->s.ownerNum = ENTNUM( self );
	bolt->touch = W_Touch_Bolt;
	bolt->classname = "bolt";
	bolt->s.renderfx |= RF_FULLBRIGHT;
	bolt->style = mod;

	W_ProjectilePrestep( bolt );

	return bolt;
}

//==================
//W_Fire_Instagun_Strong
//==================
void W_Fire_Instagun_Strong( edict_t *self, vec3_t start, vec3_t aimdir, float speed, int damage, int knockback, int radius, int range, int dflags, int mod, int timeDelta )
{
	vec3_t from;
	vec3_t end;
	trace_t	tr;
	edict_t	*ignore, *event;
	int mask;
	qboolean missed = qtrue;

	VectorMA( start, range, aimdir, end );
	VectorCopy( start, from );
	ignore = self;
	mask = MASK_SHOT;
	if( game.gametype == GAMETYPE_RACE )
		mask = MASK_SOLID;
	tr.ent = -1;
	while( ignore )
	{
		G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta );
		VectorCopy( tr.endpos, from );
		ignore = NULL;
		if( tr.ent == -1 )
			break;
		// some entity was touched
		if( tr.ent == world->s.number 
			|| game.edicts[tr.ent].movetype == MOVETYPE_NONE 
			|| game.edicts[tr.ent].movetype == MOVETYPE_PUSH )
		{
			event = G_SpawnEvent( EV_INSTA_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos );
			event->s.firemode = FIRE_MODE_STRONG;
			if( g_instajump->integer )
			    T_RadiusDamage( event, self, &tr.plane, 1.0f, knockback, 0.0f, NULL, radius, mod );
			break;
		}

		// allow trail to go through SOLID_BBOX entities (players, gibs, etc)
		if( game.edicts[tr.ent].r.solid == SOLID_BBOX )
			ignore = &game.edicts[tr.ent];

		if( ( &game.edicts[tr.ent] != self ) && ( game.edicts[tr.ent].takedamage ) )
		{
			T_Damage( &game.edicts[tr.ent], self, self, aimdir, tr.endpos, tr.plane.normal, damage, knockback, dflags, mod );
			// spawn a impact event on each damaged ent
			event = G_SpawnEvent( EV_INSTA_EXPLOSION, DirToByte( tr.plane.normal ), tr.endpos );
			event->s.firemode = FIRE_MODE_STRONG;
			if( game.edicts[tr.ent].r.client )
				missed = qfalse;
		}
	}

	if( missed )
		G_AwardPlayerMissedElectrobolt( self, mod );

	// send the weapon fire effect
	event = G_SpawnEvent( EV_INSTATRAIL, ( tr.ent != -1 ) ? qtrue : qfalse, start );
	event->r.svflags = SVF_TRANSMITORIGIN2;
	VectorCopy( from, event->s.origin2 );
	event->s.ownerNum = ENTNUM( self ); // identify who fired for cgame

}

//==================
//W_Fire_Instagun_Weak
//==================
edict_t *W_Fire_Instagun_Weak( edict_t *self, vec3_t start, vec3_t aimdir, float speed, int damage, int knockback, int timeout, int dflags, int mod, int timeDelta )
{
	edict_t	*bolt;

	// projectile, weak mode
	bolt = W_Fire_LinearProjectile( self, start, aimdir, speed, damage, knockback, 0, 0, timeout, timeDelta );
	bolt->s.modelindex = trap_ModelIndex( PATH_INSTAGUN_WEAK_MODEL );
	bolt->s.type = ET_ELECTRO_WEAK; //add particle trail and light
	bolt->s.ownerNum = ENTNUM( self );
	bolt->touch = W_Touch_Bolt;
	bolt->classname = "bolt";
	bolt->s.renderfx |= RF_FULLBRIGHT;
	bolt->style = mod;

	W_ProjectilePrestep( bolt );

	return bolt;
}

//==================
//G_FreeLaser
//==================
static void G_FreeLaser( edict_t *ent )
{
	assert( ent && ent->s.ownerNum );

	G_FreeEdict( ent );
}

//==================
//G_HideLaser
//==================
void G_HideLaser( edict_t *ent )
{
	int soundindex;

	ent->s.modelindex = 0;
	ent->s.sound = 0;
	ent->r.svflags |= SVF_NOCLIENT;

	if( ent->s.type == ET_CURVELASERBEAM )
		soundindex = trap_SoundIndex( S_WEAPON_LASERGUN_W_STOP );
	else
		soundindex = trap_SoundIndex( S_WEAPON_LASERGUN_S_STOP );
	G_Sound( game.edicts + ent->s.ownerNum, CHAN_AUTO, soundindex, 1, ATTN_NORM );

	// give it 100 msecs before freeing itself, so we can relink it if we start firing again
	ent->think = G_FreeLaser;
	ent->nextthink = level.time + 100;
}

//==================
//G_HideClientLaser
//==================
void G_HideClientLaser( edict_t *ent )
{
	edict_t	*e;
	int i;

	for( i = game.maxclients; i < game.maxentities; i++ )
	{
		e = &game.edicts[i];
		if( !e->r.inuse )
			continue;

		if( e->s.ownerNum == ENTNUM( ent ) && ( e->s.type == ET_CURVELASERBEAM || e->s.type == ET_LASERBEAM ) &&
		    e->s.modelindex )
		{
			G_HideLaser( e );
			break;
		}
	}
}

//==================
//G_Laser_Think
//==================
static void G_Laser_Think( edict_t *ent )
{
	edict_t *owner = &game.edicts[ent->s.ownerNum];

	assert( ent->s.ownerNum && ent->s.modelindex );

	if( !owner->r.inuse || owner->r.svflags & SVF_NOCLIENT || owner->s.team == TEAM_SPECTATOR || owner->deadflag )
	{
		G_HideLaser( ent );
		return;
	}

	ent->nextthink = level.time + 100;
}

//==================
//W_Fire_Lasergun
//==================
void W_Fire_Lasergun( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int knockback, int range, int dflags, int mod, int timeDelta )
{
	int i, playernum, mask;
	edict_t	*e, *laser, *ignore;
	trace_t	tr;
	vec3_t end, from;
	qboolean missed = qtrue;

	// first of all, see if we already have a beam entity for this laser
	laser = NULL;
	playernum = ENTNUM( self );
	for( i = game.maxclients; i < game.maxentities; i++ )
	{
		e = &game.edicts[i];
		if( !e->r.inuse )
			continue;

		if( e->s.ownerNum == playernum && ( e->s.type == ET_LASERBEAM || e->s.type == ET_CURVELASERBEAM ) )
		{
			laser = e;
			break;
		}
	}

	// if no ent was found we have to create one
	if( !laser || laser->s.type == ET_CURVELASERBEAM || !laser->s.modelindex )
	{
		// we send the muzzleflash event only when new laserbeam is created
		if( !laser )
		{
			G_AddEvent( self, EV_MUZZLEFLASH, FIRE_MODE_STRONG, qtrue );
			if( self->r.client->quad_timeout > level.time )
				G_Sound( self, CHAN_AUTO, trap_SoundIndex( S_QUAD_FIRE ), 1, ATTN_NORM );
			laser = G_Spawn();
		}

		laser->s.type = ET_LASERBEAM;
		laser->s.ownerNum = playernum;
		laser->movetype = MOVETYPE_NONE;
		laser->r.solid = SOLID_NOT;
		laser->r.svflags = SVF_TRANSMITORIGIN2;
		laser->s.modelindex = 255; // needs to have some value so it isn't filtered by the server culling
	}
	if( self->r.client->quad_timeout > level.time )
	{
		laser->s.sound = trap_SoundIndex( S_WEAPON_LASERGUN_S_QUAD_HUM );
	}
	else
	{
		laser->s.sound = trap_SoundIndex( S_WEAPON_LASERGUN_S_HUM );
	}

	VectorMA( start, range, aimdir, end );
	VectorCopy( start, from );
	ignore = self;
	mask = MASK_SHOT;
	if( game.gametype == GAMETYPE_RACE )
		mask = MASK_SOLID;
	while( ignore )
	{
		G_Trace4D( &tr, from, NULL, NULL, end, ignore, mask, timeDelta );
		if( tr.ent == -1 )
		{
			ignore = NULL;
		}
		else
		{ // some entity was touched
			// allow trail to go through SOLID_BBOX entities (players, gibs, etc)
			if( game.edicts[tr.ent].r.solid == SOLID_BBOX )
				ignore = &game.edicts[tr.ent];
			else
				ignore = NULL;

			if( ( &game.edicts[tr.ent] != self ) && game.edicts[tr.ent].takedamage )
			{
				T_Damage( &game.edicts[tr.ent], self, self, aimdir, tr.endpos, tr.plane.normal, damage, knockback, dflags,
				          mod );
				if( game.edicts[tr.ent].r.client )
					missed = qfalse;
			}
		}

		VectorCopy( tr.endpos, from );
	}

	VectorCopy( from, laser->s.origin );
	VectorCopy( start, laser->s.origin2 ); // used in the case the owner isn't in the PVS
	laser->s.range = range;
	laser->think = G_Laser_Think;
	laser->nextthink = level.time + 100;

	if( missed )
		G_AwardPlayerMissedLasergun( self, mod );

	GClip_LinkEntity( laser );
}

//void AITools_DrawLine(vec3_t origin, vec3_t dest);
void W_Fire_Lasergun_Weak( edict_t *self, vec3_t start, vec3_t end, vec3_t aimdir, int damage, int knockback, int range, int dflags, int mod, int timeDelta )
{
	int i, playernum;
	edict_t	*e, *laser;
	vec3_t from;
	qboolean missed = qtrue;

	// first of all, see if we already have a beam entity for this laser
	laser = NULL;
	playernum = ENTNUM( self );
	for( i = game.maxclients; i < game.maxentities; i++ )
	{
		e = &game.edicts[i];
		if( !e->r.inuse )
			continue;

		if( e->s.ownerNum == playernum && ( e->s.type == ET_CURVELASERBEAM || e->s.type == ET_LASERBEAM ) )
		{
			laser = e;
			break;
		}
	}

	// if no ent was found we have to create one
	if( !laser || laser->s.type == ET_LASERBEAM || !laser->s.modelindex )
	{
		// we send the muzzleflash event only when new laserbeam is created
		if( !laser )
		{
			G_AddEvent( self, EV_MUZZLEFLASH, FIRE_MODE_WEAK, qtrue );
			if( self->r.client->quad_timeout > level.time )
				G_Sound( self, CHAN_AUTO, trap_SoundIndex( S_QUAD_FIRE ), 1, ATTN_NORM );
			laser = G_Spawn();
		}

		laser->s.type = ET_CURVELASERBEAM;
		laser->s.teleported = qtrue;
		laser->s.ownerNum = playernum;
		laser->movetype = MOVETYPE_NONE;
		laser->r.solid = SOLID_NOT;
		laser->r.svflags = SVF_TRANSMITORIGIN2;
		laser->s.modelindex = 255; // needs to have some value so it isn't filtered by server culling
	}
	if( self->r.client->quad_timeout > level.time )
	{
		laser->s.sound = trap_SoundIndex( S_WEAPON_LASERGUN_W_QUAD_HUM );
	}
	else
	{
		laser->s.sound = trap_SoundIndex( S_WEAPON_LASERGUN_W_HUM );
	}

	// trace the beam curve
	{
		int j;
		float subdivisions = CURVELASERBEAM_SUBDIVISIONS, frac;
		vec3_t dir, impactangles, tmpangles;
		vec3_t segmentStart, segmentEnd;
		edict_t	*segmentIgnore;

		VectorSubtract( end, start, dir );
		VecToAngles( dir, impactangles );

		segmentIgnore = self;

		VectorCopy( start, segmentStart );
		for( i = 1; i <= (int)subdivisions; i++ )
		{
			frac = ( ( (float)range/subdivisions )*(float)i ) / (float)range;
			for( j = 0; j < 3; j++ ) tmpangles[j] = LerpAngle( self->s.angles[j], impactangles[j], frac );
			AngleVectors( tmpangles, dir, NULL, NULL );
			VectorMA( start, range*frac, dir, segmentEnd );

			//segment is ready here
			{
				trace_t	trace;
				edict_t	*ignore;
				int mask;

				VectorCopy( segmentStart, from );
				ignore = segmentIgnore;
				mask = MASK_SHOT;
				if( game.gametype == GAMETYPE_RACE )
					mask = MASK_SOLID;
				while( ignore )
				{
					G_Trace4D( &trace, from, NULL, NULL, segmentEnd, ignore, mask, timeDelta );
					if( trace.ent == -1 )
					{
						ignore = NULL;
					}
					// if it can't take damage, or blocked for unknown reason, stop dead
					else if( game.edicts[trace.ent].takedamage == DAMAGE_NO || VectorCompare( segmentStart, segmentEnd ) )
					{
						VectorCopy( trace.endpos, from );
						goto beamfinished;
					}
					else
					{ // some entity was touched
						// allow trail to go through SOLID_BBOX entities (players, gibs, etc)
						if( ( game.edicts[trace.ent].r.svflags & SVF_MONSTER ) || ( game.edicts[trace.ent].r.client ) ||
						   ( game.edicts[trace.ent].r.solid == SOLID_BBOX ) )
						{
							ignore = &game.edicts[trace.ent];
							segmentIgnore = ignore;
						}
						else
							ignore = NULL;

						if( ( &game.edicts[trace.ent] != self ) && ( game.edicts[trace.ent].takedamage ) )
						{
							T_Damage( &game.edicts[trace.ent], self, self, dir, trace.endpos, trace.plane.normal, damage, knockback, dflags, mod );
							// spawn a impact event on each damaged ent
							//event = G_SpawnEvent( EV_BOLT_EXPLOSION, DirToByte(trace.plane.normal), self->s.origin );
							//event->s.firemode = FIRE_MODE_STRONG;
							if( game.edicts[trace.ent].r.client )
								missed = qfalse;
						}
					}

					VectorCopy( trace.endpos, from );
					// if found the world, stop
					if( ignore == NULL && trace.fraction < 1.0f )
					{
						goto beamfinished;
					}
				}
			}

			//AITools_DrawLine( segmentStart, from );
			// copy start point for next segment
			VectorCopy( segmentEnd, segmentStart );
		}
	}

beamfinished:
	// NOTE: We send the start point as origin and and goal point as origin2
	// strong laser works differently
	VectorCopy( start, laser->s.origin );
	VectorCopy( end, laser->s.origin2 );
	VectorCopy( self->s.angles, laser->s.angles ); // (need player angles for curved) used in the case the owner isn't in the PVS
	laser->s.range = range;
	//laser->s.skinnum = (int)VectorLengthFast( from );
	laser->think = G_Laser_Think;
	laser->nextthink = level.time + 100;

	if( missed )
		G_AwardPlayerMissedLasergun( self, mod );

	GClip_LinkEntity( laser );
}
