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

Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).  

RTCW MP Source Code 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 3 of the License, or
(at your option) any later version.

RTCW MP Source Code 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 RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

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

/*
 * name:		g_misc.c
 *
 * desc:
 *
*/


#include "g_local.h"

extern void AimAtTarget( gentity_t * self );

int sniper_sound;
int snd_noammo;

/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience.  They are turned into normal brushes by the utilities.
*/


/*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_camp( gentity_t *self ) {
	G_SetOrigin( self, self->s.origin );
}


/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
*/
void SP_info_null( gentity_t *self ) {
	G_FreeEntity( self );
}


/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
Used as a positional target for in-game calculation, like jumppad targets.
target_position does the same thing
*/
void SP_info_notnull( gentity_t *self ) {
	G_SetOrigin( self, self->s.origin );
}


/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point q3map_non-dynamic
Non-displayed light.
"light" overrides the default 300 intensity.
Nonlinear checkbox gives inverse square falloff instead of linear
Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
"fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
"q3map_non-dynamic" specifies that this light should not contribute to the world's 'light grid' and therefore will not light dynamic models in the game.(wolf)
*/
void SP_light( gentity_t *self ) {
	G_FreeEntity( self );
}

/*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
"light" overrides the default 300 intensity.
Nonlinear checkbox gives inverse square falloff instead of linear
Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
Lights pointed at a target will be spotlights.
"radius" overrides the default 64 unit radius of a spotlight at the target point.
"fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
*/
void SP_lightJunior( gentity_t *self ) {
	G_FreeEntity( self );
}



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

TELEPORTERS

=================================================================================
*/
void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
	gentity_t   *tent;
	qboolean noAngles;

	noAngles = (angles[0] > 999999.0);
	// use temp events at source and destination to prevent the effect
	// from getting dropped by a second player event
	if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
		tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
		tent->s.clientNum = player->s.clientNum;

		tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
		tent->s.clientNum = player->s.clientNum;
	}

	// unlink to make sure it can't possibly interfere with G_KillBox
	trap_UnlinkEntity( player );

	VectorCopy( origin, player->client->ps.origin );
	player->client->ps.origin[2] += 1;

	if (!noAngles) {
	// spit the player out
	AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
	VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
	player->client->ps.pm_time = 160;       // hold time
	player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;

	// set angles
	SetClientViewAngle(player, angles);
	}
	// toggle the teleport bit so the client knows to not lerp
	player->client->ps.eFlags ^= EF_TELEPORT_BIT;

	// kill anything at the destination
	if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
		G_KillBox( player );
	}

	// save results of pmove
	BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );

	// use the precise origin for linking
	VectorCopy( player->client->ps.origin, player->r.currentOrigin );

	if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
		trap_LinkEntity( player );
	}
}


/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
Point teleporters at these.
Now that we don't have teleport destination pads, this is just
an info_notnull
*/
void SP_misc_teleporter_dest( gentity_t *ent ) {
}


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

	misc_grabber_trap

*/


static int attackDurations[] = {    ( 11 * 1000 ) / 15,
									( 16 * 1000 ) / 15,
									( 16 * 1000 ) / 15 };

static int attackHittimes[] = {     ( 7 * 1000 ) / 15,
									( 6 * 1000 ) / 15,
									( 7 * 1000 ) / 15 };

/*
==============
grabber_think_idle
	think func for the grabber ent to reset to idle if not attacking
==============
*/
void grabber_think_idle( gentity_t *ent ) {
	if ( ent->s.frame > 1 ) {  // non-idle status
		ent->s.frame = rand() % 2;
	}
}

/*
==============
grabber_think_hit
	think func for grabber ent following an attack command
==============
*/
void grabber_think_hit( gentity_t *ent ) {
	G_RadiusDamage( ent->s.pos.trBase, ent, ent->damage, ent->duration, ent, MOD_GRABBER );
	G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); // sound2to1 is the 'pain' sound

	ent->nextthink  = level.time + ( attackDurations[( ent->s.frame ) - 2] - attackHittimes[( ent->s.frame ) - 2] );
	ent->think      = grabber_think_idle;
}


/*
==============
grabber_die
==============
*/
extern void GibEntity( gentity_t * self, int killer ) ;

void grabber_die( gentity_t *ent, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {

	// FIXME FIXME
	// this is buggy.  the trigger brush entity (ent->enemy) does not free.
	// need to fix.

	GibEntity( ent, 0 );    // use temporarily to show 'death' of entity

//	trap_UnlinkEntity(ent->enemy);
	ent->enemy->think = G_FreeEntity;
	ent->enemy->nextthink = level.time + FRAMETIME;
//	G_FreeEntity(ent->enemy);

	G_UseTargets( ent, attacker );

//	trap_UnlinkEntity(ent);
	ent->think = G_FreeEntity;
	ent->nextthink = level.time + FRAMETIME;
//	G_FreeEntity(ent);
}




/*
==============
grabber_attack
	direct call to the grabber entity (not a trigger) to call the attack
==============
*/
void grabber_attack( gentity_t *ent ) {
	ent->s.frame    = ( rand() % 3 ) + 2;   // randomly choose an attack sequence

	ent->nextthink  = level.time + attackHittimes[( ent->s.frame ) - 2];
	ent->think      = grabber_think_hit;
}

/*
==============
grabber_close
	touch func for attack distance trigger entity
==============
*/
void grabber_close( gentity_t *ent, gentity_t *other, trace_t *trace ) {
	if ( ent->parent->nextthink > level.time ) {
		return;
	}

	grabber_attack( ent->parent );
}



/*
==============
grabber_pain
	pain func for the grabber entity (not triggers)
==============
*/
void grabber_pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) {
	G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); // sound2to1 is the 'pain' sound
}


/*
==============
grabber_wake
	ent calling this is the bounding box for the grabber, not the grabber ent itself.
	the grabber ent is 'ent->parent'
==============
*/
void grabber_wake( gentity_t *ent ) {
	gentity_t *parent;

	parent = ent->parent;

	// change the 'a' trigger to the 'b' trigger for grabber attacking
	VectorCopy( parent->s.origin, ent->r.mins );
	VectorCopy( parent->s.origin, ent->r.maxs );

	if ( 1 ) {     // temp fast trigger
		VectorAdd( ent->r.mins, tv( -( ent->random ), -( ent->random ), -( ent->random ) ), ent->r.mins );
		VectorAdd( ent->r.maxs, tv( ent->random, ent->random, ent->random ), ent->r.maxs );
	}

	ent->touch = grabber_close;

	// parent entity: show model/play anim/take damage
	{
		parent->clipmask    = CONTENTS_SOLID;
		parent->r.contents  = CONTENTS_SOLID;
		parent->takedamage  = qtrue;
		parent->active      = qtrue;
		parent->die         = grabber_die;
		parent->pain        = grabber_pain;
		trap_LinkEntity( parent );

		ent->s.frame        = 5;    // starting position

		// go back to an idle if not attacking immediately
		parent->nextthink   = level.time + FRAMETIME;
		parent->think       = grabber_think_idle;
	}

	G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos1 ); // soundPos1 is the 'wake' sound
}


/*
==============
grabber_use
	use func for the grabber entity
	if not awake, allow waking by trigger
	if awake, allow attacking by trigger
==============
*/
void grabber_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	G_Printf( "grabber_use: %d\n", level.time );

	if ( !ent->active ) {
		grabber_wake( ent );
	} else {
		grabber_attack( ent );
	}
}

/*
==============
grabber_wake_touch
	touch func for the first 'wake' trigger entity
==============
*/
void grabber_wake_touch( gentity_t *ent, gentity_t *other, trace_t *trace ) {
	grabber_wake( ent );
}


/*QUAKED misc_grabber_trap (1 0 0) (-8 -8 -8) (8 8 8)
fields:
"adist"  - radius of 'wakeup' box.  player passing closer than distance activates grabber (def: 64)
"bdist"  - radius of 'attack' box.  player passing into this gets a swipe.  (def: 32)
"health" - how much damage grabber can take after 'wakeup' (def: 100)
"range"  - when attacking, how far from the origin the grabber can strike (def: 64)
"dmg"    - max damage to give on a successful strike (def: 10)
"wait"   - how long to wait between strikes if the player stays within the 'attack' box (def: see below)

If you do not set a "wait" value, then it will default to the duration of the animations.  (so since the first attack animation is 11 frames long and plays at 15 fps, the default wait after using attack 1 would be 11/15, or 0.73 seconds)

grabber media:
model - "models/misc/grabber/grabber.md3"
wake sound - "models/misc/grabber/grabber_wake.wav"
attack sound - "models/misc/grabber/grabber_attack.wav"
pain sound - "models/misc/grabber/grabber_pain.wav"

The current frames are:
first frame
|   length
	|   looping frames
		|   fps
			|   damage at frame
				|
0   6   6   5   0  (main idle)
5   21  21  7   0  (random idle)
25  11  10  15  7  (attack big swipe)
35  16  0   15  6  (attack small swipe)
50  16  0   15  7  (attack grab)
66  1   1   15  0  (starting position)

*/
void SP_misc_grabber_trap( gentity_t *ent ) {
	int adist, bdist, range;
	gentity_t   *trig;

	// TODO: change from 'trap' to something else.  'trap' is a misnomer.  it's actually used for other stuff too
	ent->s.eType        = ET_TRAP;

	// TODO: make these user assignable?
	ent->s.modelindex   = G_ModelIndex( "models/misc/grabber/grabber.md3" );
	ent->soundPos1      = G_SoundIndex( "models/misc/grabber/grabber_wake.wav" );
	ent->sound1to2      = G_SoundIndex( "models/misc/grabber/grabber_attack.wav" );
	ent->sound2to1      = G_SoundIndex( "models/misc/grabber/grabber_pain.wav" );

	G_SetOrigin( ent, ent->s.origin );
	VectorCopy( ent->s.angles, ent->s.apos.trBase );
	ent->s.apos.trBase[YAW] -= 90;  // adjust for model rotation


	if ( !ent->health ) {
		ent->health = 100;  // default to 100

	}
	if ( !ent->damage ) {
		ent->damage = 10;   // default to 10

	}
	ent->s.frame    = 5;

	ent->use        = grabber_use;  // allow 'waking' from trigger

	VectorSet( ent->r.mins, -12, -12, 0 );   // target area for shooting it after it wakes
	VectorSet( ent->r.maxs, 12, 12, 48 );

	// create the 'a' trigger for waking up the grabber
	trig = ent->enemy = G_Spawn();

	VectorCopy( ent->s.origin, trig->r.mins );
	VectorCopy( ent->s.origin, trig->r.maxs );

	// store attack range in 'duration'
	G_SpawnInt( "range", "64", &range );
	ent->duration = range;

	// store adist/bdist in 'count/random' of the trigger brush ent
	G_SpawnInt( "adist", "64", &adist );
	trig->count = adist;
	G_SpawnInt( "bdist", "32", &bdist );
	trig->random = bdist;

	// just make an even trigger box around the ent (do properly sized/oriented trigger after it's working)
	if ( 1 ) {     // temp fast trigger
		VectorAdd( trig->r.mins, tv( -( trig->count ), -( trig->count ), -( trig->count ) ), trig->r.mins );
		VectorAdd( trig->r.maxs, tv( trig->count, trig->count, trig->count ), trig->r.maxs );
	}

	trig->parent        = ent;
	trig->r.contents    = CONTENTS_TRIGGER;
	trig->r.svFlags     = SVF_NOCLIENT;
	trig->touch         = grabber_wake_touch;
	trap_LinkEntity( trig );

}

void use_spotlight( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	gentity_t   *tent;

	if ( ent->r.linked ) {
		trap_UnlinkEntity( ent );
	} else
	{
		tent = G_PickTarget( ent->target );
		VectorCopy( tent->s.origin, ent->s.origin2 );

		ent->active = 0;
		trap_LinkEntity( ent );
	}
}


void spotlight_finish_spawning( gentity_t *ent ) {
	if ( ent->spawnflags & 1 ) {   // START_ON
		ent->active = 0;
		trap_LinkEntity( ent );
	}

	ent->use        = use_spotlight;
	ent->think      = 0;
	ent->nextthink  = 0;
}


//----(SA)	added
/*QUAKED misc_spotlight (1 0 0) (-16 -16 -16) (16 16 16) START_ON BACK_AND_FORTH
"model" - 'base' model that moves with the light.  Default: "models/mapobjects/light/searchlight_pivot.md3"
"target" - .camera (spline) file for light to track.  do not specify file extension.

BACK_AND_FORTH - when end of target spline is hit, reverse direction rather than looping (looping is default)
( /\ not active yet /\ )
*/
void SP_misc_spotlight( gentity_t *ent ) {

	ent->s.eType        = ET_EF_SPOTLIGHT;

	ent->think = spotlight_finish_spawning;
	ent->nextthink = level.time + 100;

	if ( ent->model ) {
		ent->s.modelindex   = G_ModelIndex( ent->model );
	} else {
		ent->s.modelindex   = G_ModelIndex( "models/mapobjects/light/searchlight_pivot.md3" );
	}

	if ( ent->target ) {
		ent->s.density = G_FindConfigstringIndex( ent->target, CS_SPLINES, MAX_SPLINE_CONFIGSTRINGS, qtrue );
	}

}

//----(SA)	end

//===========================================================

/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
"model"		arbitrary .md3 file to display
"modelscale"	scale multiplier (defaults to 1x)
"modelscale_vec"	scale multiplier (defaults to 1 1 1, scales each axis as requested)

"modelscale_vec" - Set scale per-axis.  Overrides "modelscale", so if you have both, the "modelscale" is ignored
*/
void SP_misc_model( gentity_t *ent ) {
	G_FreeEntity( ent );
}


//----(SA)
/*QUAKED misc_gamemodel (1 0 0) (-16 -16 -16) (16 16 16) ORIENT_LOD
md3 placed in the game at runtime (rather than in the bsp)
"model"			arbitrary .md3 file to display
"modelscale"	scale multiplier (defaults to 1x, and scales uniformly)
"modelscale_vec"	scale multiplier (defaults to 1 1 1, scales each axis as requested)
"trunk"			diameter of solid core (used for trace visibility and collision (not ai pathing))
"trunkheight"	height of trunk
ORIENT_LOD - if flagged, the entity will yaw towards the player when the LOD switches

"modelscale_vec" - Set scale per-axis.  Overrides "modelscale", so if you have both, the "modelscale" is ignored

*/
void SP_misc_gamemodel( gentity_t *ent ) {

	float scale[3] = {1,1,1};
	vec3_t scalevec;
	int trunksize, trunkheight;

	ent->s.eType        = ET_GAMEMODEL;
	ent->s.modelindex   = G_ModelIndex( ent->model );

	// look for general scaling
	if ( G_SpawnFloat( "modelscale", "1", &scale[0] ) ) {
		scale[2] = scale[1] = scale[0];
	}

	// look for axis specific scaling
	if ( G_SpawnVector( "modelscale_vec", "1 1 1", &scalevec[0] ) ) {
		VectorCopy( scalevec, scale );
	}

	G_SpawnInt( "trunk", "0", &trunksize );
	if ( !G_SpawnInt( "trunkhight", "0", &trunkheight ) ) {
		trunkheight = 256;
	}

	if ( trunksize ) {
		float rad;

		ent->clipmask       = CONTENTS_SOLID;
		ent->r.contents     = CONTENTS_SOLID;

		ent->r.svFlags |= SVF_CAPSULE;

		rad = (float)trunksize / 2.0f;
		VectorSet( ent->r.mins, -rad, -rad, 0 );
		VectorSet( ent->r.maxs, rad, rad, trunkheight );
	}

	// scale is stored in 'angles2'
	VectorCopy( scale, ent->s.angles2 );

	G_SetOrigin( ent, ent->s.origin );
	VectorCopy( ent->s.angles, ent->s.apos.trBase );

	if ( ent->spawnflags & 1 ) {
		ent->s.apos.trType = 1; // misc_gamemodels (since they have no movement) will use type = 0 for static models, type = 1 for auto-aligning models


	}
	trap_LinkEntity( ent );

}




//----(SA)

void locateMaster( gentity_t *ent ) {
	ent->target_ent = G_Find( NULL, FOFS( targetname ), ent->target );
	if ( ent->target_ent ) {
		ent->s.otherEntityNum = ent->target_ent->s.number;
	} else {
		G_Printf( "Couldn't find target(%s) for misc_vis_dummy at %s\n", ent->target, vtos( ent->r.currentOrigin ) );
		G_FreeEntity( ent );
	}
}

/*QUAKED misc_vis_dummy (1 .5 0) (-8 -8 -8) (8 8 8)
If this entity is "visible" (in player's PVS) then it's target is forced to be active whether it is in the player's PVS or not.
This entity itself is never visible or transmitted to clients.
For safety, you should have each dummy only point at one entity (however, it's okay to have many dummies pointing at one entity)
*/
void SP_misc_vis_dummy( gentity_t *ent ) {

	if ( !ent->target ) { //----(SA)	added safety check
		G_Printf( "No target specified for misc_vis_dummy at %s\n", vtos( ent->r.currentOrigin ) );
		G_FreeEntity( ent );
		return;
	}

	ent->r.svFlags |= SVF_VISDUMMY;
	G_SetOrigin( ent, ent->s.origin );
	trap_LinkEntity( ent );

	ent->think = locateMaster;
	ent->nextthink = level.time + 1000;

}

//----(SA) end

/*QUAKED misc_vis_dummy_multiple (1 .5 0) (-8 -8 -8) (8 8 8)
If this entity is "visible" (in player's PVS) then it's target is forced to be active whether it is in the player's PVS or not.
This entity itself is never visible or transmitted to clients.
This entity was created to have multiple speakers targeting it
*/
void SP_misc_vis_dummy_multiple( gentity_t *ent ) {
	if ( !ent->targetname ) {
		G_Printf( "misc_vis_dummy_multiple needs a targetname at %s\n", vtos( ent->r.currentOrigin ) );
		G_FreeEntity( ent );
		return;
	}

	ent->r.svFlags |= SVF_VISDUMMY_MULTIPLE;
	G_SetOrigin( ent, ent->s.origin );
	trap_LinkEntity( ent );

}


//===========================================================

//----(SA)
/*QUAKED misc_light_surface (1 .5 0) (-8 -8 -8) (8 8 8)
The surfaces nearest these entities will be the only surfaces lit by the targeting light
This must be within 64 world units of the surface to be lit!
*/
void SP_misc_light_surface( gentity_t *ent ) {
	G_FreeEntity( ent );
}

//----(SA) end

//===========================================================

void locateCamera( gentity_t *ent ) {
	vec3_t dir;
	gentity_t   *target;
	gentity_t   *owner;

	owner = G_PickTarget( ent->target );
	if ( !owner ) {
		G_Printf( "Couldn't find target for misc_partal_surface\n" );
		G_FreeEntity( ent );
		return;
	}
	ent->r.ownerNum = owner->s.number;

	// frame holds the rotate speed
	if ( owner->spawnflags & 1 ) {
		ent->s.frame = 25;
	} else if ( owner->spawnflags & 2 ) {
		ent->s.frame = 75;
	}

	// clientNum holds the rotate offset
	ent->s.clientNum = owner->s.clientNum;

	VectorCopy( owner->s.origin, ent->s.origin2 );

	// see if the portal_camera has a target
	target = G_PickTarget( owner->target );
	if ( target ) {
		VectorSubtract( target->s.origin, owner->s.origin, dir );
		VectorNormalize( dir );
	} else {
		G_SetMovedir( owner->s.angles, dir );
	}

	ent->s.eventParm = DirToByte( dir );
}

/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
This must be within 64 world units of the surface!
*/
void SP_misc_portal_surface( gentity_t *ent ) {
	VectorClear( ent->r.mins );
	VectorClear( ent->r.maxs );
	trap_LinkEntity( ent );

	ent->r.svFlags = SVF_PORTAL;
	ent->s.eType = ET_PORTAL;

	if ( !ent->target ) {
		VectorCopy( ent->s.origin, ent->s.origin2 );
	} else {
		ent->think = locateCamera;
		ent->nextthink = level.time + 100;
	}
}

/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate
The target for a misc_portal_director.  You can set either angles or target another entity to determine the direction of view.
"roll" an angle modifier to orient the camera around the target vector;
*/
void SP_misc_portal_camera( gentity_t *ent ) {
	float roll;

	VectorClear( ent->r.mins );
	VectorClear( ent->r.maxs );
	trap_LinkEntity( ent );

	G_SpawnFloat( "roll", "0", &roll );

	ent->s.clientNum = roll / 360.0 * 256;
}

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

  SHOOTERS

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

void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	vec3_t dir;
	float deg;
	vec3_t up, right;

	// see if we have a target
	if ( ent->enemy ) {
		VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
		if ( ent->s.weapon != WP_SNIPER ) {
			VectorNormalize( dir );
		}
	} else {
		VectorCopy( ent->movedir, dir );
	}

	if ( ent->s.weapon == WP_MORTAR ) {
		AimAtTarget( ent );   // store in ent->s.origin2 the direction/force needed to pass through the target
		VectorCopy( ent->s.origin2, dir );
	}

	if ( ent->s.weapon != WP_SNIPER ) {
		// randomize a bit
		PerpendicularVector( up, dir );
		CrossProduct( up, dir, right );

		deg = crandom() * ent->random;
		VectorMA( dir, deg, up, dir );

		deg = crandom() * ent->random;
		VectorMA( dir, deg, right, dir );

		VectorNormalize( dir );
	}

	switch ( ent->s.weapon ) {
	case WP_GRENADE_LAUNCHER:
		VectorScale( dir, 700, dir );                 //----(SA)	had to add this as fire_grenade now expects a non-normalized direction vector
		fire_grenade( ent, ent->s.origin, dir, WP_GRENADE_LAUNCHER );
		break;
	case WP_PANZERFAUST:
	case WP_ROCKET_LAUNCHER:
		fire_rocket( ent, ent->s.origin, dir );
		break;

	case WP_SPEARGUN:
	case WP_SPEARGUN_CO2:
		fire_speargun( ent, ent->s.origin, dir );
		break;

		// Rafael sniper
	case WP_SNIPER:
		fire_lead( ent, ent->s.origin, dir, ent->damage );
		break;
		// done

	case WP_MORTAR:
		AimAtTarget( ent );   // store in ent->s.origin2 the direction/force needed to pass through the target
		VectorScale( dir, VectorLength( ent->s.origin2 ), dir );
		fire_mortar( ent, ent->s.origin, dir );
		break;

	}

	G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
}

static void InitShooter_Finish( gentity_t *ent ) {
	ent->enemy = G_PickTarget( ent->target );
	ent->think = 0;
	ent->nextthink = 0;
}

void InitShooter( gentity_t *ent, int weapon ) {
	ent->use = Use_Shooter;
	ent->s.weapon = weapon;

	// Rafael sniper
	if ( weapon != WP_SNIPER ) {
		RegisterItem( BG_FindItemForWeapon( weapon ) );
	}
	// done

	G_SetMovedir( ent->s.angles, ent->movedir );

	if ( !ent->random ) {
		ent->random = 1.0;
	}

	if ( ent->s.weapon != WP_SNIPER ) {
		ent->random = sin( M_PI * ent->random / 180 );
	}

	// target might be a moving object, so we can't set movedir for it
	if ( ent->target ) {
		ent->think = InitShooter_Finish;
		ent->nextthink = level.time + 500;
	}
	trap_LinkEntity( ent );
}

/*QUAKED shooter_mortar (1 0 0) (-16 -16 -16) (16 16 16) SMOKE_FX FLASH_FX
Lobs a mortar so that it will pass through the info_notnull targeted by this entity
"random" the number of degrees of deviance from the taget. (1.0 default)
if LAUNCH_FX is checked a smoke effect will play at the origin of this entity.
if FLASH_FX is checked a muzzle flash effect will play at the origin of this entity.
*/
void SP_shooter_mortar( gentity_t *ent ) {
	// (SA) TODO: must have a self->target.  Do a check/print if this is not the case.
	InitShooter( ent, WP_MORTAR );

	if ( ent->spawnflags & 1 ) {   // smoke at source
	}
	if ( ent->spawnflags & 2 ) {   // muzzle flash at source
	}
}

/*QUAKED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_rocket( gentity_t *ent ) {
	InitShooter( ent, WP_ROCKET_LAUNCHER );
}

/*QUAKED shooter_zombiespit (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_zombiespit( gentity_t *ent ) {
	InitShooter( ent, WP_MONSTER_ATTACK1 );
}


/*
==============
use_shooter_tesla
==============
*/
void use_shooter_tesla( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	gentity_t   *tent;

	if ( ent->r.linked ) {
		trap_UnlinkEntity( ent );
	} else
	{
		tent = G_PickTarget( ent->target );
		VectorCopy( tent->s.origin, ent->s.origin2 );

		ent->active = 0;
		trap_LinkEntity( ent );
	}
}

//----(SA)	added
/*QUAKED shooter_tesla (1 0 0) (-16 -16 -16) (16 16 16) START_ON DLIGHT
START_ON means it starts out active, the default is to start off and fire when triggered
DLIGHT will have a built-in dlight flashing too (use color picker to set color of dlight)

"sticktime" - how long each bolt should 'stick' to an impact point (def: .5)
"random" - how far away to drift from the target. (def: 0.0)
"width" - width of the bolts (def: 20)
"count" - number of bolts to fire per impact point.  (def: 2)
*/

void shooter_tesla_finish_spawning( gentity_t *ent ) {
	gentity_t   *tent;  // target ent

	ent->think = 0;
	ent->nextthink = 0;

	// locate the target and set the location
	tent = G_PickTarget( ent->target );
	if ( !tent ) { // if there's a problem with tent
		G_Printf( "shooter_tesla (%s) at %s has no target.\n", ent->target, vtos( ent->s.origin ) );
		return;
	}

	VectorCopy( tent->s.origin, ent->s.origin2 );

	if ( ent->spawnflags & 1 ) {   // START_ON
		ent->active = 0;
		trap_LinkEntity( ent );
	}
}

void SP_shooter_tesla( gentity_t *ent ) {

	float tempf;

	//	it's a shooter_ since it will act like any other shooter, except
	//	the tesla is a client-side effect that reports damage back to the
	//	game, so this will create an linked-entity on the client that acts as dictated
	//	and, if necessary, passes back damage info like the weapon.  this will
	//	keep the server->client messages to a minimum (start firing/stop firing)
	//	rather than sending an event for each bolt.
	ent->s.eType        = ET_EF_TESLA;
	ent->use            = use_shooter_tesla;

	// set number of bolts
	if ( ent->count ) {
		ent->s.density = ent->count;
	} else {
		ent->s.density = 2;
	}


	// width
	if ( G_SpawnFloat( "width", "", &tempf ) ) {
		ent->s.frame = (int)tempf;
	} else {
		ent->s.frame = 20;
	}


	// 'sticky' time (stored in .weapon)
	if ( G_SpawnFloat( "sticktime", "", &tempf ) ) {
		ent->s.time2 = (int)( tempf * 1000.0f );
	} else {
		ent->s.time2 = 500; // default to 1/2 sec

	}
	// randomness
	ent->s.angles2[0] = ent->random;


	// DLIGHT
	if ( ent->spawnflags & 2 ) {
		int dlightsize;
		if ( G_SpawnInt( "dlightsize", "", &dlightsize ) ) {
			ent->s.time = dlightsize;
		} else {
			ent->s.time = 500;
		}

		if ( ent->random ) {
			ent->s.time2 = ent->random;
		} else {
			ent->s.time2 = 4; // dlight randomness

		}
		if ( ent->dl_color[0] <= 0 &&    // if it's black or has no color assigned
			 ent->dl_color[1] <= 0 &&
			 ent->dl_color[2] <= 0 ) {
			// default is the same color as the tesla weapon
			ent->dl_color[0] = 0.2f;
			ent->dl_color[1] = 0.6f;
			ent->dl_color[2] = 1.0f;
		}

		ent->dl_color[0] = ent->dl_color[0] * 255;
		ent->dl_color[1] = ent->dl_color[1] * 255;
		ent->dl_color[2] = ent->dl_color[2] * 255;

		ent->s.dl_intensity = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 );

	} else {
		ent->s.dl_intensity = 0;
	}


	// finish up after everything has spawned in so we know all potential targets are ready
	ent->think = shooter_tesla_finish_spawning;
	ent->nextthink = level.time + 100;
}
//----(SA)	end


/*QUAKED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
*/
void SP_shooter_grenade( gentity_t *ent ) {
	InitShooter( ent, WP_GRENADE_LAUNCHER );
}

// Rafael sniper
/*QUAKED shooter_sniper (1 0 0) (-16 -16 -16) (16 16 16)
Fires at either the target or the current direction.
"random" is the number of degrees of deviance from the taget. (1.0 default)
"damage" the amount of damage sniper will cause when he hits his target default is 10
"radius" is the dist the target would need to travel before sniper lost his beat default 256
"delay"	 is the rate of fire defaults to 1 sec
*/
void SP_shooter_sniper( gentity_t *ent ) {

	char        *damage;

	if ( G_SpawnString( "damage", "0", &damage ) ) {
		ent->damage = atoi( damage );
	}

	if ( !ent->damage ) {
		ent->damage = 10;
	}
	if ( !ent->radius ) { // radius
		ent->radius = 256;
	}
	if ( !ent->delay ) {
		ent->delay = 1.0; // one sec

	}
	InitShooter( ent, WP_SNIPER );

	ent->delay *= 1000;

	ent->wait = level.time + ent->delay;
}

void brush_activate_sniper( gentity_t *ent, gentity_t *other, trace_t *trace ) {
	gentity_t *sniper;
	float dist;
	vec3_t vec;
	gentity_t *player;

	player = AICast_FindEntityForName( "player" );

	if ( player && player != other ) {
		// G_Printf ("other: %s\n", other->aiName);
		return;
	}

	if ( other->client ) {
		ent->enemy = other;
	}

	sniper = G_Find( NULL, FOFS( targetname ), ent->target );

	if ( !sniper ) {
		G_Printf( "sniper not found:\n" );
	} else
	{
		if ( visible( sniper, other ) ) {
			if ( sniper->wait < level.time ) {
				if ( sniper->count == 0 ) {
					sniper->count = 1;
					sniper->wait = level.time + sniper->delay;
					// record enemypos pos
					VectorCopy( ent->enemy->r.currentOrigin, ent->pos1 );
				} else if ( sniper->count == 1 )     {
					VectorSubtract( ent->enemy->r.currentOrigin, ent->pos1, vec );
					dist = VectorLength( vec );
					if ( dist < sniper->radius ) {
						// ok the enemy is still inside the radius take a shot
						sniper->enemy = other;
						sniper->use( sniper, other, other );
						G_UseTargets( ent, other );

						// added sniper shot

						G_AddEvent( player, EV_GENERAL_SOUND, sniper_sound );

					}

					// reset the sniper delay
					sniper->count = 0;
					sniper->wait = level.time + sniper->delay;
				}
			}
		} else
		{
			//sniper->wait = level.time + sniper->delay;
			sniper->count = 0;
		}
	}

}

void sniper_brush_init( gentity_t *ent ) {
	vec3_t center;

	if ( !ent->target ) {
		VectorSubtract( ent->r.maxs, ent->r.mins, center );
		VectorScale( center, 0.5, center );

		G_Printf( "sniper_brush at %s without a target\n", vtos( center ) );
	}
}

extern void InitTrigger( gentity_t *self );

/*QUAKED sniper_brush (1 0 0) ?
this should be a volume that will encompase the area where the sniper target assigned to the
brush would fire at the player
*/
void SP_sniper_brush( gentity_t *ent ) {
	ent->nextthink = level.time + FRAMETIME;
	ent->think = sniper_brush_init;
	ent->touch = brush_activate_sniper;

	sniper_sound = G_SoundIndex( "sound/weapons/machinegun/machgf1b.wav" );

	InitTrigger( ent );
	trap_LinkEntity( ent );
}



/*QUAKED corona (0 1 0) (-4 -4 -4) (4 4 4) START_OFF
Use color picker to set color or key "color".  values are 0.0-1.0 for each color (rgb).
"scale" will designate a multiplier to the default size.  (so 2.0 is 2xdefault size, 0.5 is half)
*/

/*
==============
use_corona
	so level designers can toggle them on/off
==============
*/
void use_corona( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	if ( ent->r.linked ) {
		trap_UnlinkEntity( ent );
	} else
	{
		ent->active = 0;
		trap_LinkEntity( ent );
	}
}


/*
==============
SP_corona
==============
*/
void SP_corona( gentity_t *ent ) {
	float scale;

	ent->s.eType        = ET_CORONA;

	if ( ent->dl_color[0] <= 0 &&                // if it's black or has no color assigned
		 ent->dl_color[1] <= 0 &&
		 ent->dl_color[2] <= 0 ) {
		ent->dl_color[0] = ent->dl_color[1] = ent->dl_color[2] = 1; // set white

	}
	ent->dl_color[0] = ent->dl_color[0] * 255;
	ent->dl_color[1] = ent->dl_color[1] * 255;
	ent->dl_color[2] = ent->dl_color[2] * 255;

	ent->s.dl_intensity = (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 );

	G_SpawnFloat( "scale", "1", &scale );
	ent->s.density = (int)( scale * 255 );

	ent->use = use_corona;

	if ( !( ent->spawnflags & 1 ) ) {
		trap_LinkEntity( ent );
	}
}


// (SA) dlights and dlightstyles
// TTimo gcc: lots of braces around scalar initializer
// char* predef_lightstyles[] = {
//	{"mmnmmommommnonmmonqnmmo"},

char* predef_lightstyles[] = {
	"mmnmmommommnonmmonqnmmo",
	"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba",
	"mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
	"ma",
	"jklmnopqrstuvwxyzyxwvutsrqponmlkj",
	"nmonqnmomnmomomono",
	"mmmaaaabcdefgmmmmaaaammmaamm",
	"aaaaaaaazzzzzzzz",
	"mmamammmmammamamaaamammma",
	"abcdefghijklmnopqrrqponmlkjihgfedcba",
	"mmnommomhkmmomnonmmonqnmmo",
	"kmamaamakmmmaakmamakmakmmmma",
	"kmmmakakmmaaamammamkmamakmmmma",
	"mmnnoonnmmmmmmmmmnmmmmnonmmmmmmm",
	"mmmmnonmmmmnmmmmmnonmmmmmnmmmmmmm",
	"zzzzzzzzaaaaaaaa",
	"zzzzzzzzaaaaaaaaaaaaaaaa",
	"aaaaaaaazzzzzzzzaaaaaaaa",
	"aaaaaaaaaaaaaaaazzzzzzzz"
};


/*
==============
dlight_finish_spawning
	All the dlights should call this on the same frame, thereby
	being synched, starting	their sequences all at the same time.
==============
*/
void dlight_finish_spawning( gentity_t *ent ) {
	G_FindConfigstringIndex( va( "%i %s %i %i %i", ent->s.number, ent->dl_stylestring, ent->health, ent->soundLoop, ent->dl_atten ), CS_DLIGHTS, MAX_DLIGHT_CONFIGSTRINGS, qtrue );
}

static int dlightstarttime = 0;


/*QUAKED dlight (0 1 0) (-12 -12 -12) (12 12 12) FORCEACTIVE STARTOFF ONETIME
"style": value is an int from 1-19 that contains a pre-defined 'flicker' string.
"stylestring": set your own 'flicker' string.  (ex. "klmnmlk"). NOTE: this should be all lowercase
Stylestring characters run at 10 cps in the game. (meaning the alphabet, at 24 characters, would take 2.4 seconds to cycle)
"offset": change the initial index in a style string.  So val of 3 in the above example would start this light at 'N'.  (used to get dlights using the same style out of sync).
"atten": offset from the alpha values of the stylestring.  stylestring of "ddeeffzz" with an atten of -1 would result in "ccddeeyy"
Use color picker to set color or key "color".  values are 0.0-1.0 for each color (rgb).
FORCEACTIVE	- toggle makes sure this light stays alive in a map even if the user has r_dynamiclight set to 0.
STARTOFF	- means the dlight doesn't spawn in until ent is triggered
ONETIME		- when the dlight is triggered, it will play through it's cycle once, then shut down until triggered again
"shader" name of shader to apply
"sound" sound to loop every cycle (this actually just plays the sound at the beginning of each cycle)

styles:
1 - "mmnmmommommnonmmonqnmmo"
2 - "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"
3 - "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"
4 - "ma"
5 - "jklmnopqrstuvwxyzyxwvutsrqponmlkj"
6 - "nmonqnmomnmomomono"
7 - "mmmaaaabcdefgmmmmaaaammmaamm"
8 - "aaaaaaaazzzzzzzz"
9 - "mmamammmmammamamaaamammma"
10 - "abcdefghijklmnopqrrqponmlkjihgfedcba"
11 - "mmnommomhkmmomnonmmonqnmmo"
12 - "kmamaamakmmmaakmamakmakmmmma"
13 - "kmmmakakmmaaamammamkmamakmmmma"
14 - "mmnnoonnmmmmmmmmmnmmmmnonmmmmmmm"
15 - "mmmmnonmmmmnmmmmmnonmmmmmnmmmmmmm"
16 - "zzzzzzzzaaaaaaaa"
17 - "zzzzzzzzaaaaaaaaaaaaaaaa"
18 - "aaaaaaaazzzzzzzzaaaaaaaa"
19 - "aaaaaaaaaaaaaaaazzzzzzzz"
*/


/*
==============
shutoff_dlight
	the dlight knew when it was triggered to unlink after going through it's cycle once
==============
*/
void shutoff_dlight( gentity_t *ent ) {
	if ( !( ent->r.linked ) ) {
		return;
	}

	trap_UnlinkEntity( ent );
	ent->think = 0;
	ent->nextthink = 0;
}


/*
==============
use_dlight
==============
*/
void use_dlight( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	if ( ent->r.linked ) {
		trap_UnlinkEntity( ent );
	} else
	{
		ent->active = 0;
		trap_LinkEntity( ent );

		if ( ent->spawnflags & 4 ) {   // ONETIME
			ent->think = shutoff_dlight;
			ent->nextthink = level.time + (  strlen( ent->dl_stylestring )  * 100 ) - 100;
		}
	}
}



/*
==============
SP_dlight
	ent->dl_stylestring contains the lightstyle string
	ent->health tracks current index into style string
	ent->count tracks length of style string
==============
*/
void SP_dlight( gentity_t *ent ) {
	char    *snd, *shader;
	int i;
	int offset, style, atten;

	G_SpawnInt( "offset", "0", &offset );              // starting index into the stylestring
	G_SpawnInt( "style", "0", &style );                    // predefined stylestring
	G_SpawnString( "sound", "", &snd );                 //
	G_SpawnInt( "atten", "0", &atten );                    //
	G_SpawnString( "shader", "", &shader );             // name of shader to use for this dlight image

	if ( G_SpawnString( "sound", "0", &snd ) ) {
		ent->soundLoop = G_SoundIndex( snd );
	}

	if ( ent->dl_stylestring && strlen( ent->dl_stylestring ) ) {    // if they're specified in a string, use em
	} else if ( style )       {
		style = max( 1, style );                                  // clamp to predefined range
		style = min( 19, style );
		ent->dl_stylestring = predef_lightstyles[style - 1];    // these are input as 1-20
	} else {
		ent->dl_stylestring = "mmmaaa";                          // default to a strobe to call attention to this not being set
	}

	ent->count      = strlen( ent->dl_stylestring );

	ent->dl_atten = atten;

	// make the initial offset a valid index into the stylestring
	offset = offset % ( ent->count );

	ent->health     = offset;                   // set the offset into the string

	ent->think      = dlight_finish_spawning;
	if ( !dlightstarttime ) {                      // sync up all the dlights
		dlightstarttime = level.time + 100;
	}
	ent->nextthink  = dlightstarttime;

	if ( ent->dl_color[0] <= 0 &&                // if it's black or has no color assigned, make it white
		 ent->dl_color[1] <= 0 &&
		 ent->dl_color[2] <= 0 ) {
		ent->dl_color[0] = ent->dl_color[1] = ent->dl_color[2] = 1;
	}

	ent->dl_color[0] = ent->dl_color[0] * 255;  // range 0-255 now so the client doesn't have to on every update
	ent->dl_color[1] = ent->dl_color[1] * 255;
	ent->dl_color[2] = ent->dl_color[2] * 255;

	i = (int)( ent->dl_stylestring[offset] ) - (int)'a';
	i = i * ( 1000.0f / 24.0f );

	ent->s.constantLight =  (int)ent->dl_color[0] | ( (int)ent->dl_color[1] << 8 ) | ( (int)ent->dl_color[2] << 16 ) | ( i / 4 << 24 );

	ent->use = use_dlight;

	if ( !( ent->spawnflags & 2 ) ) {
		trap_LinkEntity( ent );
	}

}
// done (SA)



// Rafael particles
/*QUAKED misc_snow256 (1 0 0) (-256 -256 -16) (256 256 16) TURBULENT
health = density defaults to 32
*/
/*QUAKED misc_snow128 (1 0 0) (-128 -128 -16) (128 128 16) TURBULENT
health = density defaults to 32
*/
/*QUAKED misc_snow64 (1 0 0) (-64 -64 -16) (64 64 16) TURBULENT
health = density defaults to 32
*/
/*QUAKED misc_snow32 (1 0 0) (-32 -32 -16) (32 32 16) TURBULENT
health = density defaults to 32
*/

/*QUAKED misc_bubbles8 (1 0 0) (-8 -8 0) (8 8 16) TURBULENT
health = density defaults to 32
*/
/*QUAKED misc_bubbles16 (1 0 0) (-16 -16 0) (16 16 16) TURBULENT
health = density defaults to 32
*/
/*QUAKED misc_bubbles32 (1 0 0) (-32 -32 0) (32 32 16) TURBULENT
health = density defaults to 32
*/
/*QUAKED misc_bubbles64 (1 0 0) (-64 -64 0) (64 64 64) TURBULENT
health = density defaults to 32
*/


void snowInPVS( gentity_t *ent ) {
	gentity_t *tent;
	gentity_t *player;
	qboolean inPVS = qfalse;
	qboolean oldactive;

	oldactive = ent->active;

	ent->nextthink = level.time + FRAMETIME;

	player = AICast_FindEntityForName( "player" );

	if ( player ) {
		inPVS = trap_InPVS( player->r.currentOrigin, ent->r.currentOrigin );

		if ( inPVS ) {
			ent->active = qtrue;
		} else {
			ent->active = qfalse;
		}
	} else {
		return;
	}

	// there hasn't been a change so bail
	if ( oldactive == ent->active ) {
		return;
	}

	if ( ent->active ) {
		tent = G_TempEntity( player->r.currentOrigin, EV_SNOW_ON );
// G_Printf( "on\n");
	} else
	{
		tent = G_TempEntity( player->r.currentOrigin, EV_SNOW_OFF );
// G_Printf( "off\n");
	}

	tent->s.frame = ent->s.number;
	trap_LinkEntity( ent );
}

void snow_think( gentity_t *ent ) {
	trace_t tr;
	vec3_t dest;
	int turb;

	VectorCopy( ent->s.origin, dest );

	if ( ent->spawnflags & 2 ) { // bubble
		dest[2] += 8192;
	} else {
		dest[2] -= 8192;
	}

	trap_Trace( &tr, ent->s.origin, NULL, NULL, dest, ent->s.number, MASK_SHOT );

	if ( ent->spawnflags & 1 ) {
		turb = 1;
	} else {
		turb = 0;
	}

	if ( !Q_stricmp( ent->classname, "misc_snow256" ) ) {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW256, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_snow128" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW128, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_snow64" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW64, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_snow32" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_SNOW32, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_bubbles8" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE8, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_bubbles16" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE16, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_bubbles32" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE32, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	} else if ( !Q_stricmp( ent->classname, "misc_bubbles64" ) )       {
		G_FindConfigstringIndex( va( "%i %.2f %.2f %.2f %.2f %.2f %.2f %i %i %i", PARTICLE_BUBBLE64, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], tr.endpos[0], tr.endpos[1], tr.endpos[2], ent->health, turb, ent->s.number ), CS_PARTICLES, MAX_PARTICLES_AREAS, qtrue );
	}

	ent->think = snowInPVS;
	ent->nextthink = level.time + FRAMETIME;

}

void SP_Snow( gentity_t *ent ) {
	ent->think = snow_think;
	ent->nextthink = level.time + FRAMETIME;

	G_SetOrigin( ent, ent->s.origin );

	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	ent->s.eType = ET_GENERAL;

	trap_LinkEntity( ent );

	if ( !ent->health ) {
		ent->health = 32;
	}

	ent->active = qtrue;
}
// done.


void SP_Bubbles( gentity_t *ent ) {
	ent->think = snow_think;
	ent->nextthink = level.time + FRAMETIME;

	G_SetOrigin( ent, ent->s.origin );

	ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	ent->s.eType = ET_GENERAL;

	trap_LinkEntity( ent );

	if ( !ent->health ) {
		ent->health = 32;
	}

	ent->active = qtrue;

	ent->spawnflags |= 2;
}

void flakPuff( vec3_t origin, qboolean sky, vec3_t forward ) {
	gentity_t *tent;
	vec3_t point;

	VectorCopy( origin, point );
	if ( sky ) {
		VectorMA( point, -256, forward, point );
	}

	point[2] += 16; // raise puff off the ground some
	tent = G_TempEntity( point, EV_SMOKE );
	VectorCopy( point, tent->s.origin );
	tent->s.time = 2000;
	tent->s.time2 = 1000;
	tent->s.density = 0;
	tent->s.angles2[0] = 16 + 8;
	tent->s.angles2[1] = 48 + 24;
	tent->s.angles2[2] = 10;
}

/*
==============
Fire_Lead
==============
*/
//----(SA)	added 'activator' so the bits that used to expect 'ent' to be the gun still work
void Fire_Lead( gentity_t *ent, gentity_t *activator, float spread, int damage, vec3_t muzzle, vec3_t angles ) {
	trace_t tr;
	vec3_t end;
	float r;
	float u;
	gentity_t       *tent;
	gentity_t       *traceEnt;
	//qboolean	isflak = qfalse;
	int seed = rand() & 255;
	vec3_t forward, right, up;

	r = Q_crandom( &seed ) * spread;
	u = Q_crandom( &seed ) * spread;

	ent->s.eFlags |= EF_MG42_ACTIVE;
	activator->s.eFlags |= EF_MG42_ACTIVE;

	AngleVectors( angles, forward, right, up );

	VectorMA( muzzle, 8192, forward, end );
	VectorMA( end, r, right, end );
	VectorMA( end, u, up, end );

	G_HistoricalTrace( ent, &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );

	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
		AICast_ProcessBullet( activator, muzzle, tr.endpos );
	}

	if ( tr.surfaceFlags & SURF_NOIMPACT ) {
//		mg42_muzzleflash (ent);
//		G_AddEvent( ent, EV_FIRE_WEAPON_MG42, 0 );

// JPW NERVE added this event so tracers work if you're shooting mg42 into skybox, otherwise shouldn't ever see the event double-up
		tent = G_TempEntity( tr.endpos, EV_MG42BULLET_HIT_WALL );
		tent->s.otherEntityNum = ent->s.number;
		tent->s.otherEntityNum2 = activator->s.number;
		tent->s.effect1Time = seed;
// jpw

		return;
	}

	traceEnt = &g_entities[ tr.entityNum ];

	// snap the endpos to integers, but nudged towards the line
	SnapVectorTowards( tr.endpos, muzzle );

	// send bullet impact
	if ( traceEnt->takedamage && traceEnt->client ) {
		tent = G_TempEntity( tr.endpos, EV_MG42BULLET_HIT_FLESH );
		tent->s.eventParm = traceEnt->s.number;
		tent->s.otherEntityNum = ent->s.number;
		tent->s.otherEntityNum2 = activator->s.number;
		tent->s.effect1Time = seed;

		if ( LogAccuracyHit( traceEnt, ent ) ) {
			ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
		}

	} else {
		// Ridah, bullet impact should reflect off surface
		vec3_t reflect;
		float dot;

		tent = G_TempEntity( tr.endpos, EV_MG42BULLET_HIT_WALL );

		dot = DotProduct( forward, tr.plane.normal );
		VectorMA( forward, -2 * dot, tr.plane.normal, reflect );
		VectorNormalize( reflect );

		tent->s.eventParm = DirToByte( reflect );
		tent->s.otherEntityNum = ent->s.number;
		tent->s.otherEntityNum2 = activator->s.number;  // (SA) store the user id, so the client can position the tracer
		tent->s.effect1Time = seed;
	}

	if ( traceEnt->takedamage ) {
		G_Damage( traceEnt, ent, activator, forward, tr.endpos,
				  damage, 0, MOD_MACHINEGUN );
	}

//	mg42_muzzleflash (ent);
//	G_AddEvent( ent, EV_FIRE_WEAPON_MG42, 0 );
}


float AngleDifference( float ang1, float ang2 );
// NOTE: use this value, and THEN the cl_input.c scales to tweak the feel
#define MG42_IDLEYAWSPEED   80.0    // degrees per second (while returning to base)

void clamp_hweapontofirearc( gentity_t *self, vec3_t dang ) {
	float diff;

	// go back to start position
	VectorCopy( self->s.angles, dang );

	if ( dang[0] < 0 && dang[0] < -( self->varc ) ) {
		dang[0] = -( self->varc );
	}

	if ( dang[0] > 0 && dang[0] > ( self->varc / 2 ) ) {
		dang[0] = self->varc / 2;
	}

	// sanity check the angles again to make sure we don't go passed the harc
	diff = AngleDifference( self->s.angles[YAW], dang[YAW] );
	if ( fabs( diff ) > self->harc ) {

		if ( diff > 0 ) {
			dang[YAW] = AngleMod( self->s.angles[YAW] - self->harc );
		} else {
			dang[YAW] = AngleMod( self->s.angles[YAW] + self->harc );
		}
	}
}

// NOTE: this only effects the external view of the user, when using the mg42, the
// view position is set on the client-side to keep it firm behind the gun with
// interpolation
void clamp_playerbehindgun( gentity_t *self, gentity_t *other, vec3_t dang ) {
	vec3_t forward, right, up;
	vec3_t point;


	AngleVectors( self->s.apos.trBase, forward, right, up );
	VectorMA( self->r.currentOrigin, -36, forward, point );

	point[2] = other->r.currentOrigin[2];
	trap_UnlinkEntity( other );
	SnapVector( point );
	VectorCopy( point, other->client->ps.origin );

	// save results of pmove
	BG_PlayerStateToEntityState( &other->client->ps, &other->s, qfalse );

	// use the precise origin for linking
	VectorCopy( other->client->ps.origin, other->r.currentOrigin );

	// DHM - Nerve :: Zero out velocity
	other->client->ps.velocity[0] = other->client->ps.velocity[1] = 0.f;
	other->s.pos.trDelta[0] = other->s.pos.trDelta[1] = 0.f;

	trap_LinkEntity( other );
}

#define MG42_DAMAGE_MP      20          // DHM - Nerve :: testing with slowed firing rate // JPW NERVE was 30

void mg42_touch( gentity_t *self, gentity_t *other, trace_t *trace ) {
	vec3_t dang;
	int i;

	if ( !self->active ) {
		return;
	}

	if ( other->active ) {
		for ( i = 0; i < 3; i++ )
			dang[i] = SHORT2ANGLE( other->client->pers.cmd.angles[i] );

		// now tell the client to lock the view in the direction of the gun
		other->client->ps.viewlocked = 3;
		other->client->ps.viewlocked_entNum = self->s.number;

		if ( self->s.frame ) {
			other->client->ps.gunfx = 1;
		} else {
			other->client->ps.gunfx = 0;
		}

		// clamp player behind the gun
		clamp_playerbehindgun( self, other, dang );
	}
}

void mg42_fire( gentity_t *other ) {
	gentity_t *self;
	vec3_t muzzle, angles;
	vec3_t forward, up;

	self = &g_entities[other->client->ps.viewlocked_entNum];

	//VectorCopy( self->s.apos.trBase, angles );
	VectorCopy( other->client->ps.viewangles, angles );
	AngleVectors( angles, forward, NULL, up );
	VectorCopy( self->s.pos.trBase, muzzle );

	// VectorMA (muzzle, 16, forward, muzzle); // JPW NERVE unnecessary and makes it so close-range enemies get missed
	VectorMA( muzzle, 16, up, muzzle );

	self->s.eFlags |= EF_MG42_ACTIVE;
	other->s.eFlags |= EF_MG42_ACTIVE;

	// snap to integer coordinates for more efficient network bandwidth usage
	SnapVector( muzzle );

	Fire_Lead( self, other, MG42_SPREAD_MP, MG42_DAMAGE_MP, muzzle, angles );
}

void mg42_track( gentity_t *self, gentity_t *other ) {
	int i;

	if ( !self->active ) {
		return;
	}

	if ( other->active ) {
		// move to the position over the next frame
		VectorSubtract( other->client->ps.viewangles, self->s.apos.trBase, self->s.apos.trDelta );
		for ( i = 0; i < 3; i++ ) {
			self->s.apos.trDelta[i] = AngleNormalize180( self->s.apos.trDelta[i] );
		}
		VectorScale( self->s.apos.trDelta, 1000 / 50, self->s.apos.trDelta );
		self->s.apos.trTime = level.time;
		self->s.apos.trDuration = 50;

		SnapVector( self->s.apos.trDelta );
	}
}

#define USEMG42_DISTANCE 46
void mg42_think( gentity_t *self ) {
	vec3_t vec;
	gentity_t   *owner;
	int i;
	float len;
	float usedist;

	if ( level.intermissiontime ) {
		return;
	}

	VectorClear( vec );

	owner = &g_entities[self->r.ownerNum];

	// move to the current angles
	if ( self->timestamp > level.time ) {
		BG_EvaluateTrajectory( &self->s.apos, level.time, self->s.apos.trBase );
	}

	if ( owner->client ) {
		VectorSubtract( self->r.currentOrigin, owner->r.currentOrigin, vec );
		len = VectorLength( vec );

		usedist = 96;

		if ( len < usedist && owner->active && owner->health > 0 ) {
			// ATVI Wolfenstein Misc #433
			owner->client->ps.pm_flags &= ~PMF_DUCKED;

			self->active = qtrue;
			owner->client->ps.persistant[PERS_HWEAPON_USE] = 1;
			mg42_track( self, owner );
			self->nextthink = level.time + 50;
			self->timestamp = level.time + 1000;

			if ( !( owner->r.svFlags & SVF_CASTAI ) ) {
				clamp_playerbehindgun( self, owner, vec3_origin );
			}
			return;
		}
	}

	self->active = qfalse;

	if ( owner->client ) {
		owner->client->ps.persistant[PERS_HWEAPON_USE] = 0;
		owner->client->ps.viewlocked = 0;   // let them look around
		owner->active = qfalse;
		owner->client->ps.gunfx = 0;
	}

	self->r.ownerNum = self->s.number;
	self->s.otherEntityNum = self->s.number;

	if ( self->timestamp > level.time ) {
		// slowly rotate back to position
		clamp_hweapontofirearc( self, vec );
		// move to the position over the next frame
		VectorSubtract( vec, self->s.apos.trBase, self->s.apos.trDelta );
		for ( i = 0; i < 3; i++ ) {
			self->s.apos.trDelta[i] = AngleNormalize180( self->s.apos.trDelta[i] );
		}
		VectorScale( self->s.apos.trDelta, 1000 / 50, self->s.apos.trDelta );
		self->s.apos.trTime = level.time;
		self->s.apos.trDuration = 50;
	}
	self->nextthink = level.time + 50;

	SnapVector( self->s.apos.trDelta );
}

void mg42_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {
	gentity_t   *gun;
	gentity_t   *owner;
	trace_t tr;

	// DHM - Nerve :: self->chain not set if no tripod
	if ( self->chain ) {
		gun = self->chain;
	} else {
		gun = self;
	}
	// dhm - end

	owner = &g_entities[gun->r.ownerNum];

	if ( gun && self->health <= 0 ) {
		gun->s.frame = 2;
		gun->takedamage = qfalse;

		// DHM - Nerve :: health is used in repairing later
		if ( g_gametype.integer >= GT_WOLF ) {
			gun->health = 0;
			gun->s.eFlags = EF_SMOKING;         // Make it smoke on client side
			self->health = 0;
		}
		// dhm - end
	}

	self->takedamage = qfalse;

	if ( owner && owner->client ) {
		// Restore original position if current position is bad
		trap_Trace( &tr, owner->r.currentOrigin, owner->r.mins, owner->r.maxs, owner->r.currentOrigin, owner->s.number, MASK_PLAYERSOLID );
		if ( tr.startsolid ) {
			VectorCopy( owner->TargetAngles, owner->client->ps.origin );
			VectorCopy( owner->TargetAngles, owner->r.currentOrigin );
			owner->r.contents = CONTENTS_CORPSE;        // this will correct itself in ClientEndFrame
		}
		owner->client->ps.eFlags &= ~EF_MG42_ACTIVE;            // DHM - Nerve :: unset flag
		owner->client->ps.persistant[PERS_HWEAPON_USE] = 0;
		self->r.ownerNum = self->s.number;
		self->s.otherEntityNum = self->s.number;
		owner->client->ps.viewlocked = 0;   // let them look around
		owner->active = qfalse;
		owner->client->ps.gunfx = 0;

		self->active = qfalse;
		gun->active = qfalse;
	}


	trap_LinkEntity( self );
}

void mg42_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	gentity_t *owner;

	owner = &g_entities[ent->r.ownerNum];

	if ( owner && owner->client ) {
		owner->client->ps.persistant[PERS_HWEAPON_USE] = 0;
		ent->r.ownerNum = ent->s.number;
		ent->s.otherEntityNum = ent->s.number;
		owner->client->ps.viewlocked = 0;   // let them look around
		owner->active = qfalse;
		owner->client->ps.gunfx = 0;
//		other-> // JPW NERVE FIXME insert other->someUnusedVariable = ent, so we can get proper kill/score info later
	}

	// G_Printf ("mg42 called use function\n");

	trap_LinkEntity( ent );
}

void mg42_spawn( gentity_t *ent ) {
	gentity_t *base, *gun;
	vec3_t offset;

	// Xian -- If in knifeonly mode, prevent MG42's from spawning
	if ( g_knifeonly.integer != 1 ) {
		// Need to spawn the base even when no tripod cause the gun itself isn't solid
		base = G_Spawn();

		if ( !( ent->spawnflags & 2 ) ) { // no tripod
			base->clipmask = CONTENTS_SOLID;
			base->r.contents = CONTENTS_SOLID;
			base->r.svFlags = SVF_USE_CURRENT_ORIGIN;
			base->s.eType = ET_GENERAL;
			base->takedamage = qtrue;
			base->die = mg42_die;

			base->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/mg42b.md3" );
		} else {
			base->takedamage = qfalse;
		}

		VectorSet( base->r.mins, -8, -8, -8 );
		VectorSet( base->r.maxs, 8, 8, 48 );
		VectorCopy( ent->s.origin, offset );
		offset[2] -= 24;
		G_SetOrigin( base, offset );
		base->s.apos.trType = TR_STATIONARY;
		base->s.apos.trTime = 0;
		base->s.apos.trDuration = 0;
		base->s.dmgFlags = HINT_MG42;   // identify this for cursorhints
		VectorCopy( ent->s.angles, base->s.angles );
		VectorCopy( base->s.angles, base->s.apos.trBase );
		VectorCopy( base->s.angles, base->s.apos.trDelta );
		base->health = ent->health;
		base->target = ent->target; //----(SA)	added so mounting mg42 can trigger targets
		trap_LinkEntity( base );

		// Spawn the barrel
		gun = G_Spawn();
		gun->classname = "misc_mg42";
		gun->clipmask = CONTENTS_SOLID;
		gun->r.contents = CONTENTS_TRIGGER;
		gun->r.svFlags = SVF_USE_CURRENT_ORIGIN;
		gun->s.eType = ET_MG42_BARREL;
		gun->health = base->health; // JPW NERVE

		// DHM - Don't need to specify here, handled in G_CheckForCursorHints
		//gun->s.dmgFlags = HINT_MG42;	// identify this for cursorhints

		gun->touch = mg42_touch;

		gun->last_move_time = 0;
		// DHM - Nerve :: Use a different model in multiplayer
		gun->s.modelindex = G_ModelIndex( "models/multiplayer/mg42/mg42.md3" );
		VectorCopy( ent->s.origin, offset );
		offset[2] += 24;
		G_SetOrigin( gun, offset );
		VectorSet( gun->r.mins, -24, -24, -8 );
		VectorSet( gun->r.maxs, 24, 24, 48 );
		gun->s.apos.trTime = 0;
		gun->s.apos.trDuration = 0;
		VectorCopy( ent->s.angles, gun->s.angles );
		VectorCopy( gun->s.angles, gun->s.apos.trBase );
		VectorCopy( gun->s.angles, gun->s.apos.trDelta );

		VectorCopy( ent->s.angles, gun->s.angles2 );

		gun->think = mg42_think;
		gun->nextthink = level.time + FRAMETIME;
		gun->timestamp = level.time + 1000;
		gun->s.number = gun - g_entities;
		gun->harc = ent->harc;
		gun->varc = ent->varc;
		gun->s.origin2[0] = ent->harc;
		gun->s.origin2[1] = ent->varc;  // need these clientsided
		gun->s.apos.trType = TR_LINEAR_STOP;    // interpolate the angles
		gun->takedamage = qtrue;
		gun->targetname = ent->targetname;      // need this for scripting
		gun->damage = ent->damage;
		gun->accuracy = ent->accuracy;
		gun->target = ent->target;  //----(SA)	added so mounting mg42 can trigger targets
		gun->use = mg42_use;
		gun->die = mg42_die; // JPW NERVE we want it to be called for non-tripod machineguns too (for mp_beach etc)

		if ( !( ent->spawnflags & 2 ) ) { // no tripod
			gun->mg42BaseEnt = base->s.number;
		} else {
			gun->mg42BaseEnt = -1;
		}

		gun->spawnflags = ent->spawnflags;

		trap_LinkEntity( gun );

		if ( !( ent->spawnflags & 2 ) ) { // no tripod
			base->chain = gun;
		}

		G_FreeEntity( ent );
	}
}

/*QUAKED misc_mg42 (1 0 0) (-16 -16 -24) (16 16 24) HIGH NOTRIPOD
harc = horizonal fire arc Default is 57.5 (left and right)
varc = vertical fire arc Default is 45 (upwards and 22.5 down)
health = how much damage can it take default is 50
damage = determines how much the weapon will inflict if a non player uses it
"accuracy" all guns are 100% accurate an entry of 0.5 would make it 50%
*/
void SP_mg42( gentity_t *self ) {
	char        *damage;
	char        *accuracy;

	if ( !self->harc ) {
		self->harc = 57.5;
	} else
	{
		if ( self->harc < 45 ) {
			self->harc = 45;
		}
	}

	if ( !self->varc ) {
		self->varc = 45.0;
	}

	if ( !self->health ) {
		if ( g_gametype.integer == GT_SINGLE_PLAYER ) { // JPW NERVE
			self->health = 100;
		}
// JPW NERVE
		else {
			self->health = MG42_MULTIPLAYER_HEALTH;
		}
	}
// jpw

	self->think = mg42_spawn;
	self->nextthink = level.time + FRAMETIME;

	snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" );

	if ( G_SpawnString( "damage", "0", &damage ) ) {
		self->damage = atoi( damage );
	}

	G_SpawnString( "accuracy", "1.0", &accuracy );

	self->accuracy = atof( accuracy );

	if ( !self->accuracy ) {
		self->accuracy = 1;
	}
// JPW NERVE
	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
		if ( !self->damage ) {
			self->damage = 25;
		}
	}
// jpw
}

#define FLAK_SPREAD 100
#define FLAK_DAMAGE 36

#define GUN1_IDLE   0
#define GUN2_IDLE   4
#define GUN3_IDLE   8
#define GUN4_IDLE   12

#define GUN1_LASTFIRE   3
#define GUN2_LASTFIRE   7
#define GUN3_LASTFIRE   11
#define GUN4_LASTFIRE   15

void Flak_Animate( gentity_t *ent ) {
	//G_Printf ("frame %i\n", ent->s.frame);

	if ( ent->s.frame == GUN1_IDLE
		 || ent->s.frame == GUN2_IDLE
		 || ent->s.frame == GUN3_IDLE
		 || ent->s.frame == GUN4_IDLE ) {
		return;
	}

	if ( ent->count == 1 ) {
		if ( ent->s.frame == GUN1_LASTFIRE ) {
			ent->s.frame = GUN2_IDLE;
		} else if ( ent->s.frame > GUN1_IDLE ) {
			ent->s.frame++;
		}
	} else if ( ent->count == 2 )     {
		if ( ent->s.frame == GUN2_LASTFIRE ) {
			ent->s.frame = GUN3_IDLE;
		} else if ( ent->s.frame > GUN2_IDLE ) {
			ent->s.frame++;
		}
	} else if ( ent->count == 3 )     {
		if ( ent->s.frame == GUN3_LASTFIRE ) {
			ent->s.frame = GUN4_IDLE;
		} else if ( ent->s.frame > GUN3_IDLE ) {
			ent->s.frame++;
		}
	} else if ( ent->count == 4 )     {
		if ( ent->s.frame == GUN4_LASTFIRE ) {
			ent->s.frame = GUN1_IDLE;
		} else if ( ent->s.frame > GUN4_IDLE ) {
			ent->s.frame++;
		}
	}
}


void flak_spawn( gentity_t *ent ) {
	gentity_t *gun;
	vec3_t offset;

	gun = G_Spawn();
	gun->classname = "misc_flak";
	gun->clipmask = CONTENTS_SOLID;
	gun->r.contents = CONTENTS_TRIGGER;
	gun->r.svFlags = SVF_USE_CURRENT_ORIGIN;
	gun->s.eType = ET_GENERAL;
	gun->touch = mg42_touch;
	gun->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/flak_a.md3" );
	VectorCopy( ent->s.origin, offset );
	G_SetOrigin( gun, offset );
	VectorSet( gun->r.mins, -24, -24, -8 );
	VectorSet( gun->r.maxs, 24, 24, 48 );
	gun->s.apos.trTime = 0;
	gun->s.apos.trDuration = 0;
	VectorCopy( ent->s.angles, gun->s.angles );
	VectorCopy( gun->s.angles, gun->s.apos.trBase );
	VectorCopy( gun->s.angles, gun->s.apos.trDelta );
	gun->think = mg42_think;
	gun->nextthink = level.time + FRAMETIME;
	gun->s.number = gun - g_entities;
	gun->harc = ent->harc;
	gun->varc = ent->varc;
	gun->s.apos.trType = TR_LINEAR_STOP;    // interpolate the angles
	gun->takedamage = qtrue;
	gun->targetname = ent->targetname;      // need this for scripting
	gun->mg42BaseEnt = ent->s.number;

	trap_LinkEntity( gun );

}

/*QUAKED misc_flak (1 0 0) (-32 -32 0) (32 32 100)
*/
void SP_misc_flak( gentity_t *self ) {

	if ( !self->harc ) {
		self->harc = 180;
	} else
	{
		if ( self->harc < 90 ) {
			self->harc = 115;
		}
	}

	if ( !self->varc ) {
		self->varc = 90.0;
	}

	if ( !self->health ) {
		self->health = 100;
	}

	self->think = flak_spawn;
	self->nextthink = level.time + FRAMETIME;

	snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" );
}

/*QUAKED misc_spawner (.3 .7 .8) (-8 -8 -8) (8 8 8)
use the pickup name
  when this entity gets used it will spawn an item
that matches its spawnitem field
e.i.
spawnitem
9mm
*/

void misc_spawner_think( gentity_t *ent ) {

	gitem_t     *item;
	gentity_t   *drop = NULL;

	item = BG_FindItem( ent->spawnitem );

	drop = Drop_Item( ent, item, 0, qfalse );

	if ( !drop ) {
		G_Printf( "-----> WARNING <-------\n" );
		G_Printf( "misc_spawner used at %s failed to drop!\n", vtos( ent->r.currentOrigin ) );
	}

}

void misc_spawner_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {

	ent->think = misc_spawner_think;
	ent->nextthink = level.time + FRAMETIME;

//	VectorCopy (other->r.currentOrigin, ent->r.currentOrigin);
//	VectorCopy (ent->r.currentOrigin, ent->s.pos.trBase);

//	VectorCopy (other->r.currentAngles, ent->r.currentAngles);

	trap_LinkEntity( ent );
}

void SP_misc_spawner( gentity_t *ent ) {
	if ( !ent->spawnitem ) {
		G_Printf( "-----> WARNING <-------\n" );
		G_Printf( "misc_spawner at loc %s has no spawnitem!\n", vtos( ent->s.origin ) );
		return;
	}

	ent->use = misc_spawner_use;

	trap_LinkEntity( ent );

}

//===========================================================================
//
// Mounted Gunner, attached to a moving vehicle of some-sort
//

/*QUAKED misc_mounted_gunner (.4 .9 .7) (-16 -16 -24) (16 16 64) TriggerSpawn
Gunner controlling an MG42 to be mounted onto a vehicle.

Gun must be able to rotate full 360 degrees, with slight pitching.

The following entity script triggers will be called at appropriate times by the engine:

  trigger gunner_start_attack
  trigger gunner_end_attack
  trigger gunner_death

Fields:

*IMPORTANT* color = vector offest of gun mount tag on truck, from truck's origin
*IMPORTANT* delay = offset distance of gunnner mount tag on gun model, from gun's origin
target = targetname of vehicle we are attached to
aiTeam = 0 for Nazi, 1 for Allies, 2 for Monster
harc = visible arc, for initially sighting enemies Default  is 220
radius = visible distance, for sighting enemies Default is 4096

varc = vertical fire arc Default is 90 (45 above and below)
health = how much damage can it take default is 50
accuracy = all guns are 100% accurate an entry of 0.5 would make it 50%
damage = damage caused by each bullet
*/

#define MG42_SPREAD         200
#define MG42_DAMAGE_AI      9

void miscGunnerEnemyScan( gentity_t *ent, vec3_t angles ) {
	gentity_t *t;
	vec3_t v, tang;

	for ( t = g_entities; t < g_entities + level.maxclients; t++ ) {
		if ( !t->inuse ) {
			continue;
		}
		if ( t->health < 0 ) {
			continue;
		}
		if ( ent->aiTeam == t->aiTeam ) {
			continue;
		}
		if ( VectorDistance( ent->r.currentOrigin, t->r.currentOrigin ) > ent->radius ) {
			continue;
		}
		VectorSubtract( t->r.currentOrigin, ent->r.currentOrigin, v );
		vectoangles( v, tang );
		if ( !AICast_InFieldOfVision( angles, ent->harc, tang ) ) {
			continue;
		}
		if ( !AICast_VisibleFromPos( ent->r.currentOrigin, ent->s.number, t->r.currentOrigin, t->s.number, qfalse ) ) {
			continue;
		}
		// found an enemy
		ent->enemy = t;
		break;
	}
}


void miscGunnerThink( gentity_t *ent ) {
	gentity_t   *truck, *gun, *enemy;
	vec3_t vec, trang, gspot, gang;
	vec3_t fwd, r, u;
	qboolean fire = qfalse;
	float yawspeed, diff;
	vec3_t dang;
	int i;
	vec3_t muzzle;
	vec3_t forward, up;

	// find the entities
	gun = &g_entities[ent->mg42BaseEnt];
	truck = &g_entities[gun->mg42BaseEnt];

	// calculate our position based on that of the truck and gun angles
	BG_EvaluateTrajectory( &truck->s.apos, level.time, trang );
	AngleVectors( trang, fwd, r, u );
	BG_EvaluateTrajectory( &truck->s.pos, level.time, gspot );
	VectorMA( gspot, ent->dl_color[0], fwd, gspot );
	VectorMA( gspot, -ent->dl_color[1], r, gspot );
	VectorMA( gspot, ent->dl_color[2], u, gspot );
	G_SetOrigin( ent, gspot );
	G_SetOrigin( gun, gspot );

	// calculate our facing angles
	//VectorAdd( trang, gun->r.currentAngles, gang );
	if ( !gun->s.density ) {
		VectorCopy( trang, gang );
	} else {
		BG_EvaluateTrajectory( &gun->s.apos, level.time, gang );
	}

	// look for an enemy
	if ( ent->enemy ) {
		if ( ent->enemy->health <= 0 ) {
			ent->enemy = NULL;
		} else if ( AICast_VisibleFromPos( ent->r.currentOrigin, ent->s.number, ent->enemy->r.currentOrigin, ent->enemy->s.number, qfalse ) ) {
			fire = qtrue;
		}
	}

	if ( !fire ) {
		miscGunnerEnemyScan( ent, trang );
		if ( ent->enemy ) {
			fire = qtrue;

			// if we have just found our first enemy, release us from the "fixed tag" angles
			// so we can track them
			if ( !gun->s.density ) {
				gun->s.density = 1;
				// start facing the same direct, so we don't snap to a different angle immediately
				BG_EvaluateTrajectory( &truck->s.apos, level.time, ent->s.angles );
				VectorCopy( gun->s.angles, gun->s.apos.trBase );
				gun->s.apos.trTime = level.time;
				gun->s.apos.trDuration = 0;
				gun->s.apos.trType = TR_STATIONARY;
				VectorClear( gun->s.apos.trDelta );
			}
		}
	}

	// third, attack that enemy if possible
	if ( fire ) {

		// get the enemy
		enemy = ent->enemy;

		// rotate to them
		VectorSubtract( enemy->r.currentOrigin, gun->r.currentOrigin, vec );
		vectoangles( vec, gun->TargetAngles );
		VectorCopy( gun->TargetAngles, dang );
		for ( i = 0; i < 3; i++ ) {
			dang[i] = AngleNormalize180( dang[i] );
		}

		// restrict vertical range
		if ( dang[0] < 0 && fabs( dang[0] ) > ( gun->varc / 2 ) ) {
			if ( dang[0] < 0 ) {
				dang[0] = -( gun->varc / 2 );
			} else {
				dang[0] =  ( gun->varc / 2 );
			}
		}

		// dang is now the ideal angles, restrict movement by speed
		yawspeed = 60;
		for ( i = 0; i < 3; i++ ) {
			BG_EvaluateTrajectory( &gun->s.apos, level.time, gun->r.currentAngles );
			diff = AngleDifference( dang[i], gun->r.currentAngles[i] );
			if ( fabs( diff ) > ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) ) {
				if ( diff > 0 ) {
					dang[i] = AngleMod( gun->r.currentAngles[i] + ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) );
				} else {
					dang[i] = AngleMod( gun->r.currentAngles[i] - ( yawspeed * ( (float)FRAMETIME / 1000.0 ) ) );
				}
			}
		}

		// move to the position over the next frame
		VectorSubtract( dang, gun->r.currentAngles, gun->s.apos.trDelta );
		for ( i = 0; i < 3; i++ ) {
			gun->s.apos.trDelta[i] = AngleNormalize180( gun->s.apos.trDelta[i] );
		}
		VectorCopy( gun->r.currentAngles, gun->s.apos.trBase );
		VectorScale( gun->s.apos.trDelta, 1000 / 50, gun->s.apos.trDelta );
		gun->s.apos.trTime = level.time;
		gun->s.apos.trType = TR_LINEAR_STOP;
		gun->s.apos.trDuration = 50;

		// if we are facing them, fire
		if ( fabs( AngleNormalize180( gun->r.currentAngles[YAW] - gun->TargetAngles[YAW] ) ) < 10 ) {
			AngleVectors( gun->r.currentAngles, forward, NULL, up );
			VectorCopy( gspot, muzzle );

			VectorMA( muzzle, 16, forward, muzzle );
			VectorMA( muzzle, 16, up, muzzle );

			// snap to integer coordinates for more efficient network bandwidth usage
			SnapVector( muzzle );

			if ( gun->damage ) {
				Fire_Lead( gun, gun, MG42_SPREAD / gun->accuracy, gun->damage, muzzle, gun->r.currentAngles );
			} else {
				Fire_Lead( gun, gun, MG42_SPREAD / gun->accuracy, MG42_DAMAGE_AI, muzzle, gun->r.currentAngles );
			}
		}
	}

	ent->think = miscGunnerThink;
	ent->nextthink = level.time + 50;
}

void miscGunnerSpawn( gentity_t *ent ) {
	gentity_t *sp, *veh;

	veh = G_Find( NULL, FOFS( targetname ), ent->target );
	if ( !veh ) {
		G_Error( "can't find vehicle with targetname \"%s\" for mounted gunner", ent->target );
	}

	// create the ring (base)
	sp = G_Spawn();
	sp->classname = "misc_gunner_ring";
	sp->r.contents = 0;
	sp->s.eType = ET_GENERAL;
	sp->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/turret_c.md3" );
	sp->tagParent = veh;
	sp->tagName = "tag_ring";    // tag to connect to
	G_ProcessTagConnect( sp );
	trap_LinkEntity( sp );

	// create the gun
	sp = G_Spawn();
	sp->classname = "misc_gunner_gun";
	sp->r.contents = 0;
	sp->s.eType = ET_GENERAL;
	sp->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/turret_a.md3" );
	sp->tagParent = veh;
	sp->tagName = "tag_rider";   // tag to connect to
	G_ProcessTagConnect( sp );
	trap_LinkEntity( sp );

	sp->mg42BaseEnt = veh->s.number;
	sp->varc = ent->varc;
	sp->health = ent->health;
	sp->accuracy = ent->accuracy;
	sp->damage = ent->damage;

	// setup the gunner ready for action
	sp->r.contents = 0; // fixme, make damagable so we can shoot them
	sp->s.eType = ET_GENERAL;
	ent->s.modelindex = G_ModelIndex( "models/mapobjects/weapons/turret_b.md3" );
	ent->tagParent = sp;
	ent->tagName = "tag_hand";   // tag to connect to
	G_ProcessTagConnect( ent );
	trap_LinkEntity( ent );

	ent->mg42BaseEnt = sp->s.number;

	// start us thinking
	ent->think = miscGunnerThink;
	ent->nextthink = level.time + 50;
}

void miscGunnerTriggerSpawn( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	miscGunnerSpawn( ent );
}

void SP_misc_mounted_gunner( gentity_t *self ) {
	char        *damage;
	char        *accuracy;

//	if (VectorCompare(vec3_origin, self->dl_color)) {
	if ( VectorCompare( vec3_origin, self->rotate ) ) {
		G_Error( "misc_mounted_gunner requires an offset position (color field)\n" );
	}
	if ( !self->delay ) {
		G_Error( "misc_mounted_gunner requires an offset distance from gun mount (delay field)\n" );
	}

	if ( !self->harc ) {
		self->harc = 115;
	} else
	{
		if ( self->harc < 45 ) {
			self->harc = 45;
		}
	}

	if ( !self->varc ) {
		self->varc = 90.0;
	}

	if ( !self->health ) {
		self->health = 100;
	}

	if ( !self->radius ) {
		self->radius = 4096;
	}

	snd_noammo = G_SoundIndex( "sound/weapons/noammo.wav" );

	if ( G_SpawnString( "damage", "0", &damage ) ) {
		self->damage = atoi( damage );
	}

	G_SpawnString( "accuracy", "1.0", &accuracy );

	self->accuracy = atof( accuracy );

	if ( !self->accuracy ) {
		self->accuracy = 1;
	}

	// we are idle at this point
	self->enemy = NULL;

	if ( self->spawnflags & 1 ) {     // TRIGGER_SPAWN
		self->use = miscGunnerTriggerSpawn;
	} else {
		// delay the actual spawning, so we are sure everything else exists
		self->think = miscGunnerSpawn;
		self->nextthink = level.time + FRAMETIME;
	}
}

void firetrail_die( gentity_t *ent ) {
	G_FreeEntity( ent );
}

void firetrail_use( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
	if ( ent->s.eType == ET_RAMJET ) {
		ent->s.eType = ET_GENERAL;
	} else {
		ent->s.eType = ET_RAMJET;
	}

	trap_LinkEntity( ent );

}

/*QUAKED misc_firetrails (.4 .9 .7) (-16 -16 -16) (16 16 16)
This entity must target the plane its going to be attached to

  its use function will turn the fire stream effect on and off

  an alert entity call will kill it
*/

void misc_firetrails_think( gentity_t *ent ) {
	gentity_t *left, *right, *airplane;

	airplane = G_Find( NULL, FOFS( targetname ), ent->target );
	if ( !airplane ) {
		G_Error( "can't find airplane with targetname \"%s\" for firetrails", ent->target );
	}

	// left fire trail
	left = G_Spawn();
	left->classname = "left_firetrail";
	left->r.contents = 0;
	left->s.eType = ET_RAMJET;
	left->s.modelindex = G_ModelIndex( "models/ammo/rocket/rocket.md3" );
	left->tagParent = airplane;
	left->tagName = "tag_engine1";   // tag to connect to
	left->use = firetrail_use;
	left->AIScript_AlertEntity = firetrail_die;
	left->targetname = ent->targetname;
	G_ProcessTagConnect( left );
	trap_LinkEntity( left );

	// right fire trail
	right = G_Spawn();
	right->classname = "right_firetrail";
	right->r.contents = 0;
	right->s.eType = ET_RAMJET;
	right->s.modelindex = G_ModelIndex( "models/ammo/rocket/rocket.md3" );
	right->tagParent = airplane;
	right->tagName = "tag_engine2";  // tag to connect to
	right->use = firetrail_use;
	right->AIScript_AlertEntity = firetrail_die;
	right->targetname = ent->targetname;
	G_ProcessTagConnect( right );
	trap_LinkEntity( right );

}

void SP_misc_firetrails( gentity_t *ent ) {
	ent->think = misc_firetrails_think;
	ent->nextthink = level.time + 100;

}
