/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors

This file is part of the OpenJK source code.

OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

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, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#include "g_headers.h"

#include "g_local.h"
#include "g_functions.h"
#include "../cgame/cg_media.h"

extern team_t TranslateTeamName( const char *name );

//client side shortcut hacks from cg_local.h
//extern void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
extern void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType );
extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs,
							float speed, int numChunks, material_t chunkType, int customChunk, float baseScale );
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );

extern	qboolean	player_locked;

//---------------------------------------------------
static void CacheChunkEffects( material_t material )
{
	switch( material )
	{
	default:
		break;
	case MAT_GLASS:
		G_EffectIndex( "chunks/glassbreak" );
		break;
	case MAT_GLASS_METAL:
		G_EffectIndex( "chunks/glassbreak" );
		G_EffectIndex( "chunks/metalexplode" );
		break;
	case MAT_ELECTRICAL:
	case MAT_ELEC_METAL:
		G_EffectIndex( "chunks/sparkexplode" );
		break;
	case MAT_METAL:
	case MAT_METAL2:
	case MAT_METAL3:
	case MAT_CRATE1:
	case MAT_CRATE2:
		G_EffectIndex( "chunks/metalexplode" );
		break;
	case MAT_GRATE1:
		G_EffectIndex( "chunks/grateexplode" );
		break;
	case MAT_DRK_STONE:
	case MAT_LT_STONE:
	case MAT_GREY_STONE:
	case MAT_WHITE_METAL: // what is this crap really supposed to be??
		G_EffectIndex( "chunks/rockbreaklg" );
		G_EffectIndex( "chunks/rockbreakmed" );
		break;
	case MAT_ROPE:
		G_EffectIndex( "chunks/ropebreak" );
//		G_SoundIndex(); // FIXME: give it a sound
		break;
	}
}

//--------------------------------------
void funcBBrushDieGo (gentity_t *self)
{
	vec3_t		org, dir, up;
	gentity_t	*attacker = self->enemy;
	float		scale;
	int			numChunks, size = 0;
	material_t	chunkType = self->material;

	// if a missile is stuck to us, blow it up so we don't look dumb
	for ( int i = 0; i < MAX_GENTITIES; i++ )
	{
		if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
		{
			G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
		}
	}

	//So chunks don't get stuck inside me
	self->s.solid = 0;
	self->contents = 0;
	self->clipmask = 0;
	gi.linkentity(self);

	VectorSet(up, 0, 0, 1);

	if ( self->target && attacker != NULL )
	{
		G_UseTargets(self, attacker);
	}

	VectorSubtract( self->absmax, self->absmin, org );// size

	numChunks = Q_flrand(0.0f, 1.0f) * 6 + 18;

	// This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
	// Volume is length * width * height...then break that volume down based on how many chunks we have
	scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f;

	if ( scale > 48 )
	{
		size = 2;
	}
	else if ( scale > 24 )
	{
		size = 1;
	}

	scale = scale / numChunks;

	if ( self->radius > 0.0f )
	{
		// designer wants to scale number of chunks, helpful because the above scale code is far from perfect
		//	I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
		numChunks *= self->radius;
	}

	VectorMA( self->absmin, 0.5, org, org );
	VectorAdd( self->absmin,self->absmax, org );
	VectorScale( org, 0.5f, org );

	if ( attacker != NULL && attacker->client )
	{
		VectorSubtract( org, attacker->currentOrigin, dir );
		VectorNormalize( dir );
	}
	else
	{
		VectorCopy(up, dir);
	}

	if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
	{
		// we are allowed to explode
		CG_MiscModelExplosion( self->mins, self->maxs, size, chunkType );
	}

	if ( self->splashDamage > 0 && self->splashRadius > 0 )
	{
		//explode
		AddSightEvent( attacker, org, 256, AEL_DISCOVERED, 100 );
		AddSoundEvent( attacker, org, 128, AEL_DISCOVERED );
		G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );

		gentity_t *te = G_TempEntity( org, EV_GENERAL_SOUND );
		te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
	}
	else
	{//just break
		AddSightEvent( attacker, org, 128, AEL_DISCOVERED );
		AddSoundEvent( attacker, org, 64, AEL_SUSPICIOUS );
	}

	//FIXME: base numChunks off size?
	CG_Chunks( self->s.number, org, dir, self->mins, self->maxs, 300, numChunks, chunkType, 0, scale );

	gi.AdjustAreaPortalState( self, qtrue );
	self->e_ThinkFunc = thinkF_G_FreeEntity;
	self->nextthink = level.time + 50;
	//G_FreeEntity( self );
}

void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc)
{
	self->takedamage = qfalse;//stop chain reaction runaway loops

	G_SetEnemy(self, attacker);

	if(self->delay)
	{
		self->e_ThinkFunc = thinkF_funcBBrushDieGo;
		self->nextthink = level.time + floor(self->delay * 1000.0f);
		return;
	}

	funcBBrushDieGo(self);
}

void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator)
{
	G_ActivateBehavior( self, BSET_USE );
	if(self->spawnflags & 64)
	{//Using it doesn't break it, makes it use it's targets
		if(self->target && self->target[0])
		{
			G_UseTargets(self, activator);
		}
	}
	else
	{
		funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN);
	}
}

void funcBBrushPain(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod,int hitLoc)
{
	if ( self->painDebounceTime > level.time )
	{
		return;
	}

	if ( self->paintarget )
	{
		G_UseTargets2 (self, self->activator, self->paintarget);
	}

	G_ActivateBehavior( self, BSET_PAIN );

	if ( self->material == MAT_DRK_STONE
		|| self->material == MAT_LT_STONE
		|| self->material == MAT_GREY_STONE )
	{
		vec3_t	org, dir;
		float	scale;
		VectorSubtract( self->absmax, self->absmin, org );// size
		// This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
		// Volume is length * width * height...then break that volume down based on how many chunks we have
		scale = VectorLength( org ) / 100.0f;
		VectorMA( self->absmin, 0.5, org, org );
		VectorAdd( self->absmin,self->absmax, org );
		VectorScale( org, 0.5f, org );
		if ( attacker != NULL && attacker->client )
		{
			VectorSubtract( attacker->currentOrigin, org, dir );
			VectorNormalize( dir );
		}
		else
		{
			VectorSet( dir, 0, 0, 1 );
		}
		CG_Chunks( self->s.number, org, dir, self->mins, self->maxs, 300, Q_irand( 1, 3 ), self->material, 0, scale );
	}

	if ( self->wait == -1 )
	{
		self->e_PainFunc = painF_NULL;
		return;
	}

	self->painDebounceTime = level.time + self->wait;
}

static void InitBBrush ( gentity_t *ent )
{
	float		light;
	vec3_t		color;
	qboolean	lightSet, colorSet;

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

	gi.SetBrushModel( ent, ent->model );

	ent->e_DieFunc = dieF_funcBBrushDie;

	ent->svFlags |= SVF_BBRUSH;

	// if the "model2" key is set, use a seperate model
	// for drawing, but clip against the brushes
	if ( ent->model2 )
	{
		ent->s.modelindex2 = G_ModelIndex( ent->model2 );
	}

	// if the "color" or "light" keys are set, setup constantLight
	lightSet = G_SpawnFloat( "light", "100", &light );
	colorSet = G_SpawnVector( "color", "1 1 1", color );
	if ( lightSet || colorSet )
	{
		int		r, g, b, i;

		r = color[0] * 255;
		if ( r > 255 ) {
			r = 255;
		}
		g = color[1] * 255;
		if ( g > 255 ) {
			g = 255;
		}
		b = color[2] * 255;
		if ( b > 255 ) {
			b = 255;
		}
		i = light / 4;
		if ( i > 255 ) {
			i = 255;
		}
		ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 );
	}

	if(ent->spawnflags & 128)
	{//Can be used by the player's BUTTON_USE
		ent->svFlags |= SVF_PLAYER_USABLE;
	}

	ent->s.eType = ET_MOVER;
	gi.linkentity (ent);

	ent->s.pos.trType = TR_STATIONARY;
	VectorCopy( ent->pos1, ent->s.pos.trBase );
}

void funcBBrushTouch( gentity_t *ent, gentity_t *other, trace_t *trace )
{
}

/*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE IMPACT CRUSHER THIN SABERONLY HEAVY_WEAP USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
INVINCIBLE - can only be broken by being used
IMPACT - does damage on impact
CRUSHER - won't reverse movement when hit an obstacle
THIN - can be broken by impact damage, like glass
SABERONLY - only takes damage from sabers
HEAVY_WEAP - only takes damage by a heavy weapon, like an emplaced gun or AT-ST gun.
USE_NOT_BREAK - Using it doesn't make it break, still can be destroyed by damage
PLAYER_USE - Player can use it with the use button
NO_EXPLOSION - Does not play an explosion effect, though will still create chunks if specified

When destroyed, fires it's trigger and chunks and plays sound "noise" or sound for type if no noise specified

"targetname" entities with matching target will fire it
"paintarget" target to fire when hit (but not destroyed)
"wait"		how long minimum to wait between firing paintarget each time hit
"delay"		When killed or used, how long (in seconds) to wait before blowing up (none by default)
"model2"	.md3 model to also draw
"target"	all entities with a matching targetname will be used when this is destoryed
"health"	default is 10
"radius"  Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1)  (.5) is half as many chunks, (2) is twice as many chunks

Damage: default is none
"splashDamage" - damage to do
"splashRadius" - radius for above damage

"team" - This cannot take damage from members of this team:
	"player"
	"neutral"
	"enemy"

Don't know if these work:
"color"		constantLight color
"light"		constantLight radius

"material" - default is "0 - MAT_METAL" - choose from this list:
0 = MAT_METAL		(basic blue-grey scorched-DEFAULT)
1 = MAT_GLASS
2 = MAT_ELECTRICAL	(sparks only)
3 = MAT_ELEC_METAL	(METAL2 chunks and sparks)
4 =	MAT_DRK_STONE	(brown stone chunks)
5 =	MAT_LT_STONE	(tan stone chunks)
6 =	MAT_GLASS_METAL (glass and METAL2 chunks)
7 = MAT_METAL2		(electronic type of metal)
8 = MAT_NONE		(no chunks)
9 = MAT_GREY_STONE	(grey colored stone)
10 = MAT_METAL3		(METAL and METAL2 chunk combo)
11 = MAT_CRATE1		(yellow multi-colored crate chunks)
12 = MAT_GRATE1		(grate chunks--looks horrible right now)
13 = MAT_ROPE		(for yavin_trial, no chunks, just wispy bits )
14 = MAT_CRATE2		(red multi-colored crate chunks)
15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )

*/
void SP_func_breakable( gentity_t *self )
{
	if(!(self->spawnflags & 1))
	{
		if(!self->health)
		{
			self->health = 10;
		}
	}

	if ( self->spawnflags & 16 ) // saber only
	{
		self->flags |= FL_DMG_BY_SABER_ONLY;
	}
	else if ( self->spawnflags & 32 ) // heavy weap
	{
		self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY;
	}

	if (self->health)
	{
		self->takedamage = qtrue;
	}

	G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching
	G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer
	G_SpawnInt( "material", "0", (int*)&self->material );
	CacheChunkEffects( self->material );

	self->e_UseFunc = useF_funcBBrushUse;

	//if ( self->paintarget )
	{
		self->e_PainFunc = painF_funcBBrushPain;
	}

	self->e_TouchFunc = touchF_funcBBrushTouch;

	if ( self->team && self->team[0] )
	{
		self->noDamageTeam = TranslateTeamName( self->team );
		if(self->noDamageTeam == TEAM_FREE)
		{
			G_Error("team name %s not recognized", self->team);
		}
	}
	self->team = NULL;
	if (!self->model) {
		G_Error("func_breakable with NULL model");
	}
	InitBBrush( self );
}


void misc_model_breakable_pain ( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc )
{
	if ( self->health > 0 )
	{
		// still alive, react to the pain
		if ( self->paintarget )
		{
			G_UseTargets2 (self, self->activator, self->paintarget);
		}

		// Don't do script if dead
		G_ActivateBehavior( self, BSET_PAIN );
	}
}

void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
{
	int		numChunks;
	float	size = 0, scale;
	vec3_t	dir, up, dis;

	//NOTE: Stop any scripts that are currently running (FLUSH)... ?
	//Turn off animation
	self->s.frame = self->startFrame = self->endFrame = 0;
	self->svFlags &= ~SVF_ANIMATING;

	self->health = 0;

	//Throw some chunks
	AngleVectors( self->s.apos.trBase, dir, NULL, NULL );
	VectorNormalize( dir );

	numChunks = Q_flrand(0.0f, 1.0f) * 6 + 20;

	VectorSubtract( self->absmax, self->absmin, dis );

	// This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted.
	// Volume is length * width * height...then break that volume down based on how many chunks we have
	scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f;

	if ( scale > 48 )
	{
		size = 2;
	}
	else if ( scale > 24 )
	{
		size = 1;
	}

	scale = scale / numChunks;

	if ( self->radius > 0.0f )
	{
		// designer wants to scale number of chunks, helpful because the above scale code is far from perfect
		//	I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak
		numChunks *= self->radius;
	}

	VectorAdd( self->absmax, self->absmin, dis );
	VectorScale( dis, 0.5f, dis );

	CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale );

	self->e_PainFunc = painF_NULL;
	self->e_DieFunc  = dieF_NULL;
//	self->e_UseFunc  = useF_NULL;

	self->takedamage = qfalse;

	if ( !(self->spawnflags & 4) )
	{//We don't want to stay solid
		self->s.solid = 0;
		self->contents = 0;
		self->clipmask = 0;
		gi.linkentity(self);
	}

	VectorSet(up, 0, 0, 1);

	if(self->target)
	{
		G_UseTargets(self, attacker);
	}

	if(inflictor->client)
	{
		VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir );
		VectorNormalize( dir );
	}
	else
	{
		VectorCopy(up, dir);
	}

	if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION
	{
		// Ok, we are allowed to explode, so do it now!
		if(self->splashDamage > 0 && self->splashRadius > 0)
		{//explode
			vec3_t org;
			AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 );
			AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED );
			//FIXME: specify type of explosion?  (barrel, electrical, etc.)  Also, maybe just use the explosion effect below since it's
			//				a bit better?
			// up the origin a little for the damage check, because several models have their origin on the ground, so they don't alwasy do damage, not the optimal solution...
			VectorCopy( self->currentOrigin, org );
			if ( self->mins[2] > -4 )
			{//origin is going to be below it or very very low in the model
				//center the origin
				org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f;
			}
			G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN );

			if ( self->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 )
			{//TEMP HACK for Tie Fighters- they're HUGE
				G_PlayEffect( "fighter_explosion2", self->currentOrigin );
				G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) );
			}
			else
			{
				CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
				G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") );
			}
		}
		else
		{//just break
			AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED );
			AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS );
			// This is the default explosion
			CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material );
			G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"));
		}
	}

	self->e_ThinkFunc = thinkF_NULL;
	self->nextthink = -1;

	if(self->s.modelindex2 != -1 && !(self->spawnflags & 8))
	{//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist
		self->svFlags |= SVF_BROKEN;
		self->s.modelindex = self->s.modelindex2;
		G_ActivateBehavior( self, BSET_DEATH );
	}
	else
	{
		G_FreeEntity( self );
	}
}

void misc_model_use (gentity_t *self, gentity_t *other, gentity_t *activator)
{
	if ( self->health <= 0 && self->max_health > 0)
	{//used while broken fired target3
		G_UseTargets2( self, activator, self->target3 );
		return;
	}

	G_ActivateBehavior( self, BSET_USE );
	//Don't explode if they've requested it to not
	if ( self->spawnflags & 64 )
	{//Usemodels toggling
		if ( self->spawnflags & 32 )
		{
			if( self->s.modelindex == self->sound1to2 )
			{
				self->s.modelindex = self->sound2to1;
			}
			else
			{
				self->s.modelindex = self->sound1to2;
			}
		}

		return;
	}

	misc_model_breakable_die( self, other, activator, self->health, MOD_UNKNOWN );
}

#define MDL_OTHER			0
#define MDL_ARMOR_HEALTH	1
#define MDL_AMMO			2

void misc_model_breakable_init( gentity_t *ent )
{
	int		type;

	type = MDL_OTHER;

	if (!ent->model) {
		G_Error("no model set on %s at (%.1f %.1f %.1f)", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]);
	}
	//Main model
	ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );

	if ( ent->spawnflags & 1 )
	{//Blocks movement
		ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
	}
	else if ( ent->health )
	{//Can only be shot
		ent->contents = CONTENTS_SHOTCLIP;
	}

	if (type == MDL_OTHER)
	{
		ent->e_UseFunc = useF_misc_model_use;
	}
	else if ( type == MDL_ARMOR_HEALTH )
	{
//		G_SoundIndex("sound/player/suithealth.wav");
		ent->e_UseFunc = useF_health_use;
		if (!ent->count)
		{
			ent->count = 100;
		}
		ent->health = 60;
	}
	else if ( type == MDL_AMMO )
	{
//		G_SoundIndex("sound/player/suitenergy.wav");
		ent->e_UseFunc = useF_ammo_use;
		if (!ent->count)
		{
			ent->count = 100;
		}
		ent->health = 60;
	}

	if ( ent->health )
	{
		G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
		ent->max_health = ent->health;
		ent->takedamage = qtrue;
		ent->e_PainFunc = painF_misc_model_breakable_pain;
		ent->e_DieFunc  = dieF_misc_model_breakable_die;
	}
}

void TieFighterThink ( gentity_t *self )
{
	gentity_t *player = &g_entities[0];

	if ( self->health <= 0 )
	{
		return;
	}

	self->nextthink = level.time + FRAMETIME;
	if ( player )
	{
		vec3_t	playerDir, fighterDir, fwd, rt;
		float	playerDist, fighterSpeed;

		//use player eyepoint
		VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir );
		playerDist = VectorNormalize( playerDir );
		VectorSubtract( self->currentOrigin, self->lastOrigin, fighterDir );
		VectorCopy( self->currentOrigin, self->lastOrigin );
		fighterSpeed = VectorNormalize( fighterDir )*1000;
		AngleVectors( self->currentAngles, fwd, rt, NULL );

		if ( fighterSpeed )
		{
			float	side;

			// Magic number fun!  Speed is used for banking, so modulate the speed by a sine wave
			fighterSpeed *= sin( ( 100 ) * 0.003 );

			// Clamp to prevent harsh rolling
			if ( fighterSpeed > 10 )
				fighterSpeed = 10;

			side = fighterSpeed * DotProduct( fighterDir, rt );
			self->s.apos.trBase[2] -= side;
		}

		//FIXME: bob up/down, strafe left/right some
		float dot = DotProduct( playerDir, fighterDir );
		if ( dot > 0 )
		{//heading toward the player
			if ( playerDist < 1024 )
			{
				if ( DotProduct( playerDir, fwd ) > 0.7 )
				{//facing the player
					if ( self->attackDebounceTime < level.time )
					{
						gentity_t	*bolt;

						bolt = G_Spawn();

						bolt->classname = "tie_proj";
						bolt->nextthink = level.time + 10000;
						bolt->e_ThinkFunc = thinkF_G_FreeEntity;
						bolt->s.eType = ET_MISSILE;
						bolt->s.weapon = WP_BLASTER;
						bolt->owner = self;
						bolt->damage = 30;
						bolt->dflags = DAMAGE_NO_KNOCKBACK;		// Don't push them around, or else we are constantly re-aiming
						bolt->splashDamage = 0;
						bolt->splashRadius = 0;
						bolt->methodOfDeath = MOD_ENERGY;	// ?
						bolt->clipmask = MASK_SHOT;

						bolt->s.pos.trType = TR_LINEAR;
						bolt->s.pos.trTime = level.time;		// move a bit on the very first frame
						VectorCopy( self->currentOrigin, bolt->s.pos.trBase );
						VectorScale( fwd, 8000, bolt->s.pos.trDelta );
						SnapVector( bolt->s.pos.trDelta );		// save net bandwidth
						VectorCopy( self->currentOrigin, bolt->currentOrigin);

						if ( !Q_irand( 0, 2 ) )
						{
							G_SoundOnEnt( bolt, CHAN_VOICE, "sound/weapons/tie_fighter/tie_fire.wav" );
						}
						else
						{
							G_SoundOnEnt( bolt, CHAN_VOICE, va( "sound/weapons/tie_fighter/tie_fire%d.wav", Q_irand( 2, 3 ) ) );
						}
						self->attackDebounceTime = level.time + Q_irand( 300, 2000 );
					}
				}
			}
		}

		if ( playerDist < 1024 )//512 )
		{//within range to start our sound
			if ( dot > 0 )
			{
				if ( !self->fly_sound_debounce_time )
				{//start sound
					G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/tie_fighter/tiepass%d.wav", Q_irand( 1, 5 ) ) );
					self->fly_sound_debounce_time = 2000;
				}
				else
				{//sound already started
					self->fly_sound_debounce_time = -1;
				}
			}
		}
		else if ( self->fly_sound_debounce_time < level.time )
		{
			self->fly_sound_debounce_time = 0;
		}
	}
}

void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor )
{
	ent->s.eType = ET_GENERAL;
	ent->s.eFlags |= EF_BOUNCE_HALF;
	ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//?
	ent->physicsBounce = ent->mass = VectorLength( ent->maxs ) + VectorLength( ent->mins );
	//drop to floor
	trace_t		tr;
	vec3_t		top, bottom;

	if ( dropToFloor )
	{
		VectorCopy( ent->currentOrigin, top );
		top[2] += 1;
		VectorCopy( ent->currentOrigin, bottom );
		bottom[2] = MIN_WORLD_COORD;
		gi.trace( &tr, top, ent->mins, ent->maxs, bottom, ent->s.number, MASK_NPCSOLID, G2_NOCOLLIDE, 0 );
		if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 )
		{
			G_SetOrigin( ent, tr.endpos );
			gi.linkentity( ent );
		}
	}
	else
	{
		G_SetOrigin( ent, ent->currentOrigin );
		gi.linkentity( ent );
	}
	//set up for object thinking
	if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
	{//not moving
		ent->s.pos.trType = TR_STATIONARY;
	}
	else
	{
		ent->s.pos.trType = TR_GRAVITY;
	}
	VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
	VectorClear( ent->s.pos.trDelta );
	ent->s.pos.trTime = level.time;
	if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) )
	{//not moving
		ent->s.apos.trType = TR_STATIONARY;
	}
	else
	{
		ent->s.apos.trType = TR_LINEAR;
	}
	VectorCopy( ent->currentAngles, ent->s.apos.trBase );
	VectorClear( ent->s.apos.trDelta );
	ent->s.apos.trTime = level.time;
	ent->nextthink = level.time + FRAMETIME;
	ent->e_ThinkFunc = thinkF_G_RunObject;
}
/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
AUTOANIMATE - Will cycle it's anim
DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
PLAYER_USE - Player can use it with the use button
NO_EXPLOSION - By default, will explode when it dies...this is your override.

"model"		arbitrary .md3 file to display
"health"	how much health to have - default is zero (not breakable)  If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving
"targetname" when used, dies and displays damagemodel, if any (if not, removes itself)
"target" What to use when it dies
"target2" What to use when it's repaired
"target3" What to use when it's used while it's broken
"paintarget" target to fire when hit (but not destroyed)
"count"  the amount of armor/health/ammo given (default 50)
"gravity"	if set to 1, this will be affected by gravity
"radius"  Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1)  (.5) is half as many chunks, (2) is twice as many chunks

Damage: default is none
"splashDamage" - damage to do (will make it explode on death)
"splashRadius" - radius for above damage

"team" - This cannot take damage from members of this team:
	"player"
	"neutral"
	"enemy"

"material" - default is "8 - MAT_NONE" - choose from this list:
0 = MAT_METAL		(grey metal)
1 = MAT_GLASS
2 = MAT_ELECTRICAL	(sparks only)
3 = MAT_ELEC_METAL	(METAL chunks and sparks)
4 =	MAT_DRK_STONE	(brown stone chunks)
5 =	MAT_LT_STONE	(tan stone chunks)
6 =	MAT_GLASS_METAL (glass and METAL chunks)
7 = MAT_METAL2		(blue/grey metal)
8 = MAT_NONE		(no chunks-DEFAULT)
9 = MAT_GREY_STONE	(grey colored stone)
10 = MAT_METAL3		(METAL and METAL2 chunk combo)
11 = MAT_CRATE1		(yellow multi-colored crate chunks)
12 = MAT_GRATE1		(grate chunks--looks horrible right now)
13 = MAT_ROPE		(for yavin_trial, no chunks, just wispy bits )
14 = MAT_CRATE2		(red multi-colored crate chunks)
15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
FIXME/TODO:
set size better?
multiple damage models?
custom explosion effect/sound?
*/
void SP_misc_model_breakable( gentity_t *ent )
{
	char	damageModel[MAX_QPATH];
	char	chunkModel[MAX_QPATH];
	char	useModel[MAX_QPATH];
	int		len;

	// Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this.
	G_SpawnInt( "material", "8", (int*)&ent->material );
	G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer
	CacheChunkEffects( ent->material );

	misc_model_breakable_init( ent );

	len = strlen( ent->model ) - 4;
	strncpy( damageModel, ent->model, len );
	damageModel[len] = 0;	//chop extension
	strncpy( chunkModel, damageModel, sizeof(chunkModel));
	strncpy( useModel, damageModel, sizeof(useModel));

	if (ent->takedamage) {
		//Dead/damaged model
		if( !(ent->spawnflags & 8) ) {	//no dmodel
			strcat( damageModel, "_d1.md3" );
			ent->s.modelindex2 = G_ModelIndex( damageModel );
		}

		//Chunk model
		strcat( chunkModel, "_c1.md3" );
		ent->s.modelindex3 = G_ModelIndex( chunkModel );
	}

	//Use model
	if( ent->spawnflags & 32 ) {	//has umodel
		strcat( useModel, "_u1.md3" );
		ent->sound1to2 = G_ModelIndex( useModel );
	}
	if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] )
	{
		VectorSet (ent->mins, -16, -16, -16);
	}
	if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] )
	{
		VectorSet (ent->maxs, 16, 16, 16);
	}

	if ( ent->spawnflags & 2 )
	{
		ent->s.eFlags |= EF_ANIM_ALLFAST;
	}

	G_SetOrigin( ent, ent->s.origin );
	G_SetAngles( ent, ent->s.angles );
	gi.linkentity (ent);

	if ( ent->spawnflags & 128 )
	{//Can be used by the player's BUTTON_USE
		ent->svFlags |= SVF_PLAYER_USABLE;
	}

	if ( ent->team && ent->team[0] )
	{
		ent->noDamageTeam = TranslateTeamName( ent->team );
		if ( ent->noDamageTeam == TEAM_FREE )
		{
			G_Error("team name %s not recognized", ent->team);
		}
	}

	ent->team = NULL;

	//HACK
	if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 )
	{//run a think
		G_EffectIndex( "fighter_explosion2" );
		G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" );
		G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" );
		ent->e_ThinkFunc = thinkF_TieFighterThink;
		ent->nextthink = level.time + FRAMETIME;
	}
	float grav = 0;
	G_SpawnFloat( "gravity", "0", &grav );
	if ( grav )
	{//affected by gravity
		G_SetAngles( ent, ent->s.angles );
		G_SetOrigin( ent, ent->currentOrigin );
		misc_model_breakable_gravity_init( ent, qtrue );
	}
}


//----------------------------------
//
// Breaking Glass Technology
//
//----------------------------------

// Really naughty cheating.  Put in an EVENT at some point...
extern void cgi_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal );
extern void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius );
extern	cgs_t			cgs;

//-----------------------------------------------------
void funcGlassDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
{
	vec3_t		verts[4], normal;

	// if a missile is stuck to us, blow it up so we don't look dumb....we could, alternately, just let the missile drop off??
	for ( int i = 0; i < MAX_GENTITIES; i++ )
	{
		if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK ))
		{
			G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD?
		}
	}

	// Really naughty cheating.  Put in an EVENT at some point...
	cgi_R_GetBModelVerts( cgs.inlineDrawModel[self->s.modelindex], verts, normal );
	CG_DoGlass( verts, normal, self->pos1, self->pos2, self->splashRadius );

	self->takedamage = qfalse;//stop chain reaction runaway loops

	G_SetEnemy( self, self->enemy );

	//So chunks don't get stuck inside me
	self->s.solid = 0;
	self->contents = 0;
	self->clipmask = 0;
	gi.linkentity(self);

	if ( self->target && attacker != NULL )
	{
		G_UseTargets( self, attacker );
	}

	gi.AdjustAreaPortalState( self, qtrue );
	G_FreeEntity( self );
}

//-----------------------------------------------------
void funcGlassUse( gentity_t *self, gentity_t *other, gentity_t *activator )
{
	vec3_t temp1, temp2;

	// For now, we just break on use
	G_ActivateBehavior( self, BSET_USE );

	VectorAdd( self->mins, self->maxs, temp1 );
	VectorScale( temp1, 0.5f, temp1 );

	VectorAdd( other->mins, other->maxs, temp2 );
	VectorScale( temp2, 0.5f, temp2 );

	VectorSubtract( temp1, temp2, self->pos2 );
	VectorCopy( temp1, self->pos1 );

	VectorNormalize( self->pos2 );
	VectorScale( self->pos2, 390, self->pos2 );

	self->splashRadius = 40; // ?? some random number, maybe it's ok?

	funcGlassDie( self, other, activator, self->health, MOD_UNKNOWN );
}

//-----------------------------------------------------
/*QUAKED func_glass (0 .8 .5) ? INVINCIBLE
When destroyed, fires it's target, breaks into glass chunks and plays glass noise
For now, instantly breaks on impact

INVINCIBLE - can only be broken by being used

"targetname" entities with matching target will fire it
"target"	all entities with a matching targetname will be used when this is destroyed
"health"	default is 1
*/
//-----------------------------------------------------
void SP_func_glass( gentity_t *self )
{
	if ( !(self->spawnflags & 1 ))
	{
		if ( !self->health )
		{
			self->health = 1;
		}
	}

	if ( self->health )
	{
		self->takedamage = qtrue;
	}

	self->e_UseFunc = useF_funcGlassUse;
	self->e_DieFunc = dieF_funcGlassDie;

	VectorCopy( self->s.origin, self->pos1 );

	gi.SetBrushModel( self, self->model );
	self->svFlags |= (SVF_GLASS_BRUSH|SVF_BBRUSH);
	self->material = MAT_GLASS;

	self->s.eType = ET_MOVER;

	self->s.pos.trType = TR_STATIONARY;
	VectorCopy( self->pos1, self->s.pos.trBase );

	G_SoundIndex( "sound/effects/glassbreak1.wav" );
	G_EffectIndex( "glass_impact" );

	gi.linkentity( self );
}

qboolean G_EntIsBreakable( int entityNum )
{
	if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD )
	{
		return qfalse;
	}

	gentity_t *ent = &g_entities[entityNum];
	if ( (ent->svFlags&SVF_GLASS_BRUSH) )
	{
		return qtrue;
	}
	if ( (ent->svFlags&SVF_BBRUSH) )
	{
		return qtrue;
	}
	if ( !Q_stricmp( "misc_model_breakable", ent->classname ) )
	{
		return qtrue;
	}
	if ( !Q_stricmp( "misc_maglock", ent->classname ) )
	{
		return qtrue;
	}

	return qfalse;
}
