// Copyright (C) 2001-2002 Raven Software.
//
// cg_gore.c -- handle client-side gore

#include "cg_local.h"
#if !defined(GHOUL2_SHARED_H_INC)
#include "..\ghoul2\G2_gore_shared.h"
#endif


void CG_AddGore(int type, float size, vec3_t hitloc, vec3_t hitdirection, 
				int entnum, vec3_t entposition, float entangle, void *ghoul2)
{
	SSkinGoreData goreSkin;

	memset ( &goreSkin, 0, sizeof(goreSkin) );

	goreSkin.growDuration = -1; // default expandy time
	goreSkin.goreScaleStartFraction = 1.0; // default start scale
	goreSkin.frontFaces = qtrue; // forever
	goreSkin.backFaces = qtrue; // forever
	goreSkin.lifeTime = 0;
	goreSkin.baseModelOnly = qfalse;
	
	goreSkin.currentTime = cg.time;
	goreSkin.entNum      = entnum;
	goreSkin.SSize		 = size;
	goreSkin.TSize		 = size;
	goreSkin.theta		 = flrand(0,6.28);
	goreSkin.shaderEnum  = type;

	VectorSet ( goreSkin.scale, 1, 1, 1 );

	VectorCopy ( hitdirection, goreSkin.rayDirection);

	VectorCopy ( hitloc, goreSkin.hitLocation );
	VectorCopy ( entposition, goreSkin.position );
	goreSkin.angles[YAW] = entangle;

	trap_G2API_AddSkinGore(ghoul2,&goreSkin);
}

void CG_AddGrowGore(int type, float size, int growtime, float startfrac, vec3_t hitloc, vec3_t hitdirection,
				int entnum, vec3_t entposition, float entangle, void *ghoul2)
{
	SSkinGoreData goreSkin;

	memset ( &goreSkin, 0, sizeof(goreSkin) );

	goreSkin.frontFaces = qtrue; // forever
	goreSkin.backFaces = qtrue; // forever
	goreSkin.lifeTime = 0;
	goreSkin.baseModelOnly = qfalse;
	
	goreSkin.currentTime = cg.time;
	goreSkin.entNum      = entnum;
	goreSkin.SSize		 = size;
	goreSkin.TSize		 = size;
	goreSkin.theta		 = flrand(0,6.28);
	goreSkin.shaderEnum  = type;
	goreSkin.growDuration = growtime; // default expandy time
	goreSkin.goreScaleStartFraction = startfrac; // default start scale

	VectorSet ( goreSkin.scale, 1, 1, 1 );

	VectorCopy ( hitdirection, goreSkin.rayDirection);

	VectorCopy ( hitloc, goreSkin.hitLocation );
	VectorCopy ( entposition, goreSkin.position );
	goreSkin.angles[YAW] = entangle;

	trap_G2API_AddSkinGore(ghoul2,&goreSkin);
}

void CG_AddSlashGore(int type, float angle, float ssize, float tsize, vec3_t hitloc, vec3_t hitdirection, 
					 int entnum, vec3_t entposition, float entangle, void *ghoul2)
{
	SSkinGoreData goreSkin;

	memset ( &goreSkin, 0, sizeof(goreSkin) );

	goreSkin.growDuration = -1; // default expandy time
	goreSkin.goreScaleStartFraction = 1.0; // default start scale
	goreSkin.frontFaces = qtrue; // forever
	goreSkin.backFaces = qtrue; // forever
	goreSkin.lifeTime = 0;
	goreSkin.baseModelOnly = qfalse;

	goreSkin.currentTime = cg.time;
	goreSkin.entNum      = entnum;
	goreSkin.SSize		 = ssize;
	goreSkin.TSize		 = tsize;
	goreSkin.theta		 = angle;
	goreSkin.shaderEnum  = type;

	VectorSet ( goreSkin.scale, 1, 1, 1 );

	VectorCopy ( hitdirection, goreSkin.rayDirection);

	VectorCopy ( hitloc, goreSkin.hitLocation );
	VectorCopy ( entposition, goreSkin.position );
	goreSkin.angles[YAW] = entangle;

	trap_G2API_AddSkinGore(ghoul2,&goreSkin);
}

void CG_AddSlashGrowGore(int type, float angle, float ssize, float tsize, int growtime, float startfrac, 
							vec3_t hitloc, vec3_t hitdirection, 
							int entnum, vec3_t entposition, float entangle, void *ghoul2)
{
	SSkinGoreData goreSkin;

	memset ( &goreSkin, 0, sizeof(goreSkin) );

	goreSkin.frontFaces = qtrue; // forever
	goreSkin.backFaces = qtrue; // forever
	goreSkin.lifeTime = 0;
	goreSkin.baseModelOnly = qfalse;

	goreSkin.growDuration = growtime; // default expandy time
	goreSkin.goreScaleStartFraction = startfrac; // default start scale
	goreSkin.currentTime = cg.time;
	goreSkin.entNum      = entnum;
	goreSkin.SSize		 = ssize;
	goreSkin.TSize		 = tsize;
	goreSkin.theta		 = angle;
	goreSkin.shaderEnum  = type;

	VectorSet ( goreSkin.scale, 1, 1, 1 );

	VectorCopy ( hitdirection, goreSkin.rayDirection);

	VectorCopy ( hitloc, goreSkin.hitLocation );
	VectorCopy ( entposition, goreSkin.position );
	goreSkin.angles[YAW] = entangle;

	trap_G2API_AddSkinGore(ghoul2,&goreSkin);
}


void CG_AddTimedGore(int type, float size, int duration, vec3_t hitloc, vec3_t hitdirection, 
					int entnum, vec3_t entposition, float entangle, void *ghoul2)
{
	SSkinGoreData goreSkin;

	memset ( &goreSkin, 0, sizeof(goreSkin) );

	goreSkin.growDuration = -1; // default expandy time
	goreSkin.goreScaleStartFraction = 1.0; // default start scale
	goreSkin.frontFaces = qtrue; // forever
	goreSkin.backFaces = qtrue; // forever
	goreSkin.baseModelOnly = qfalse;

	goreSkin.currentTime = cg.time;
	goreSkin.entNum      = entnum;
	goreSkin.SSize		 = size;
	goreSkin.TSize		 = size;
	goreSkin.theta		 = flrand(0,6.28);
	goreSkin.shaderEnum  = type;
	goreSkin.lifeTime	 = duration;

	VectorSet ( goreSkin.scale, 1, 1, 1 );

	VectorCopy ( hitdirection, goreSkin.rayDirection);

	VectorCopy ( hitloc, goreSkin.hitLocation );
	VectorCopy ( entposition, goreSkin.position );
	goreSkin.angles[YAW] = entangle;

	trap_G2API_AddSkinGore(ghoul2,&goreSkin);
}


void CG_DoGoreFromWeapon( int weaponnum, int attack, vec3_t hitloc, vec3_t hitdirection, 
						 int entnum, vec3_t entposition, float entangle, void *ghoul2)
{
	float angle, size, size2;

	switch (weaponnum)
	{
	case WP_KNIFE:
		if (attack==ATTACK_ALTERNATE)
		{
			CG_AddGore(PGORE_PUNCTURE, flrand(3.5, 4.0), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>0)
			{
				CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.0*1.4, 15000, 0.1,
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		else
		{
			angle= (M_PI / 2 * (1+2*irand(0,1))) + flrand( .7, .7);
			switch(irand(1,3))
			{
			case 1:
				size = flrand(2.8, 3.2);
				size2 = flrand(1.8, 2.2);
				CG_AddSlashGore(PGORE_KNIFESLASH, 
						angle, size, size2, 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				if (cg_goreDetail.integer>0)
				{
					CG_AddSlashGrowGore(PGORE_KNIFE_SOAK, angle, size*1.2, size2*2.0, 15000, 0.1,
								hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				}
				break;
			case 2:
				size = 8.0f*flrand(.8, 1.2);
				size2 = 1.75f*flrand(.8, 1.2);
				CG_AddSlashGore(PGORE_KNIFESLASH2, 
						angle, size, size2, 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				if (cg_goreDetail.integer>0)
				{
					CG_AddSlashGrowGore(PGORE_KNIFE_SOAK, angle, size*1.2, size2*2.0, 15000, 0.1,
								hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				}
				break;
			default:
				size = flrand(3.0f,4.0f);
				size2 = flrand(0.5f,1.0f);
				CG_AddSlashGore(PGORE_KNIFESLASH3, 
						angle, size, size2, 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				if (cg_goreDetail.integer>0)
				{
					CG_AddSlashGrowGore(PGORE_KNIFE_SOAK, angle, size*1.2, size2*2.0, 15000, 0.1,
								hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				}
				break;
			}
		}
		break;

	// Smaller guns with pistol whip altfires
	case WP_M1911A1_PISTOL:
	case WP_SILVER_TALON:
	case WP_USSOCOM_PISTOL:
		if (attack==ATTACK_ALTERNATE)
		{	// Bonk on the head
			CG_AddGore(PGORE_BLOODY_SPLOTCH2, flrand(5.25f, 7.5f), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);

		}
		else
		{
			CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 3.75f, 4.5f), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>0)
			{
				CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.5*1.35, 15000, 0.1f,
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		break;

	// Small guns
	case WP_MICRO_UZI_SUBMACHINEGUN:
		CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 3.75f, 4.5f), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		if (cg_goreDetail.integer>0)
		{
			CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.5f*1.35f, 15000, 0.1f,
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		}
		break;

	// Shotgun with whip altfire
	case WP_M590_SHOTGUN:
		if (attack==ATTACK_ALTERNATE)
		{	// Bond on de haid
			CG_AddGore(PGORE_BLOODY_SPLOTCH2, flrand(7.75, 11.25), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		}
		else
		{
			CG_AddGore(irand(PGORE_SHOTGUN, PGORE_SHOTGUNBIG), flrand( 8.25f, 11.25f), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>0)
			{
				CG_AddGrowGore(PGORE_KNIFE_SOAK, 11.25f*1.25f, 15000, 0.1f,
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				if (cg_goreDetail.integer>1)
				{
					CG_AddGore(PGORE_PELLETS, 8.25f, 
									hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
				}
			}
		}
		break;

	// Medium guns
	case WP_M3A1_SUBMACHINEGUN:
	case WP_MP5:
	case WP_SIG551:
#ifdef SMKWEAPONS
	case WP_OICW:
#endif
		CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 5.25f, 7.5f), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		if (cg_goreDetail.integer>0)
		{
			CG_AddGrowGore(PGORE_KNIFE_SOAK, 7.5f*1.3f, 15000, 0.1f,
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		}
		break;

	// Shotguns
	case WP_USAS_12_SHOTGUN:
		CG_AddGore(irand(PGORE_SHOTGUN, PGORE_SHOTGUNBIG), flrand( 8.25f, 11.25f), 
					hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		if (cg_goreDetail.integer>0)
		{
			CG_AddGrowGore(PGORE_KNIFE_SOAK, 11.25f*1.25f, 15000, 0.1,
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>1)
			{
				CG_AddGore(PGORE_PELLETS, 8.25f, 
								hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		break;

	// Assault rifle with grenade altfire
	case WP_M4_ASSAULT_RIFLE:
		if (attack==ATTACK_ALTERNATE)
		{
			CG_AddGore(PGORE_SHRAPNEL, flrand( 14.0f, 17.0f),  
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>1)
			{
				CG_AddGore(PGORE_PELLETS, 10.0, 
								hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		else
		{
			CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 5.25f, 7.5f), 
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>0)
			{
				CG_AddGrowGore(PGORE_KNIFE_SOAK, 7.5f*1.3f, 15000, 0.1f,
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		break;

	// Assault rifle with bayonet altfire
	case WP_AK74_ASSAULT_RIFLE:
		if (attack==ATTACK_ALTERNATE)
		{
			CG_AddGore(PGORE_PUNCTURE, flrand(3.5f, 4.0f), 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>0)
			{
				CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.0f*1.4f, 15000, 0.1f,
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		else
		{
			CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 5.25f, 7.5f), 
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			if (cg_goreDetail.integer>0)
			{
				CG_AddGrowGore(PGORE_KNIFE_SOAK, 7.5f*1.3f, 15000, 0.1f,
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
			}
		}
		break;

	// Large-caliber bullets
	case WP_MSG90A1:
	case WP_M60_MACHINEGUN:
		CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 6.0f, 9.0f),
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		if (cg_goreDetail.integer>0)
		{
			CG_AddGrowGore(PGORE_KNIFE_SOAK, 9.0f*1.25f, 15000, 0.1f,
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		}
		break;

	// Explosions
	case WP_MM1_GRENADE_LAUNCHER:
	case WP_RPG7_LAUNCHER:
	case WP_SMOHG92_GRENADE:
		CG_AddGore(PGORE_SHRAPNEL, flrand( 14.0f, 17.0f),  
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		if (cg_goreDetail.integer>1)
		{
			CG_AddGore(PGORE_PELLETS, 10.0f, 
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		}
		break;

	// Stun/char
	case WP_M84_GRENADE:
	case WP_M15_GRENADE:
		CG_AddGore(PGORE_BURN, flrand( 14.0f, 18.0f),  
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		break;

	// Fire
	case WP_ANM14_GRENADE:
		CG_AddTimedGore(PGORE_IMMOLATE, flrand( 18.0f, 22.0f), 4000, 
						hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		if (cg_goreDetail.integer>0)
		{
			CG_AddGore(PGORE_BURN, flrand( 18.0f, 22.0f),  
							hitloc, hitdirection, entnum, entposition, entangle, ghoul2);
		}
		break;
	}
}


void CG_PredictedProcGore ( int weaponnum, int attack, vec3_t start, vec3_t end, centity_t* cent )
{
	vec3_t direction;

	// if no blood then dont add the proc gore
	if ( cg_lockBlood.integer || !cent->ghoul2 || !cent->currentValid)
	{
		return;
	}
	
	VectorSubtract ( end, start, direction);
	VectorNormalize ( direction);

	CG_DoGoreFromWeapon(weaponnum, attack,				// Weaponnum and altattack
			end, direction,								// hitloc and hitdirection
			cent->currentState.number,					// victim entity number
			cent->lerpOrigin, cent->pe.ghoulLegsAngles[YAW],	// entity position, entity yaw
			cent->ghoul2);
}

/*
======================
CG_AddProcGore

Adds procedural gore to the player specified in the given cent.  The cent is a 
temp ent containing all the information about the shot.
======================
*/
void CG_AddProcGore(centity_t *cent)
{
	centity_t*	  source;
	attackType_t  attack;
	vec3_t		  direction;

	// Blood locked?
	if ( cg_lockBlood.integer || !cent->currentState.time )
	{
		return;
	}

	// No procedural gore on this shot.
	if ( cent->currentState.time & GORE_NONE )
	{
		return;
	}

	source = CG_GetEntity ( cent->currentState.otherEntityNum2 );
	if (!source->ghoul2 || !source->currentValid)
	{
		return;
	}

	// Extract the direction of fire
	ByteToDir( cent->currentState.eventParm, direction );
	attack = ((cent->currentState.time>>8)&0xFF);

	CG_UpdatePlayerModel ( source );

	CG_DoGoreFromWeapon(cent->currentState.time&0xFF, attack,					// Weaponnum and altattack
			cent->lerpOrigin, direction,								// hitloc and hitdirection
			cent->currentState.otherEntityNum2,									// victim entity number
			cent->currentState.angles, (cent->currentState.time>>16)&0x7FFF,				// entity position, entity yaw
			source->ghoul2);
}



#define MAX_GORE_POOL	20000

static char		gorePool[MAX_GORE_POOL];
static int		gorePoolSize = 0;

static char *AllocGorePool(int size)
{
	gorePoolSize = ((gorePoolSize + 0x00000003) & 0xfffffffc);

	if (gorePoolSize + size > MAX_GORE_POOL)
	{
		Com_Error( ERR_DROP, "AllocGorePool: buffer exceeded (%d > %d)", gorePoolSize + size, MAX_GORE_POOL);
		return 0;
	}

	gorePoolSize += size;

	return &gorePool[gorePoolSize-size];
}

static char *AllocMultiString(TGPValue field)
{
	TGPValue	value;
	int			size = 1;
	char		name[256];
	char		*output, *pos;

	if (!field)
	{
		return 0;
	}

	value = trap_GPV_GetList(field);
	while(value)
	{
		trap_GPV_GetName(value, name);
		size += strlen(name) + 1;
		value = trap_GPV_GetNext(value);
	}

	output = pos = AllocGorePool(size);
	value = trap_GPV_GetList(field);
	while(value)
	{
		trap_GPV_GetName(value, name);
		strcpy(pos, name);
		pos += strlen(name) + 1;
		value = trap_GPV_GetNext(value);
	}
	*pos = 0;

	return output;
}








#define	GORE_CHILD						0x00000001
#define	GORE_NO_CHILD_SURFACES_ON		0x00000002
#define GORE_NO_CHILD_FX				0x00000004
#define GORE_NO_CHILD_CHUNKS			0x00000008
#define GORE_NO_CHILD_BOLTONS			0x00000010

typedef enum
{
	GORE_SIDE_RIGHT = 0,
	GORE_SIDE_LEFT,
	GORE_SIDE_MAX
} EGoreSide;

typedef struct SGoreInfo
{
	const char	*mLongName;
	const char	*mShortName;
} TGoreInfo;

typedef struct SGoreLocation
{
	const char	*mPublicName;
	EGoreSide	mPrimarySide;
	EGoreSide	mOppositeSide;
} TGoreLocation;

typedef struct SGoreEffectType
{
	char			mName[MAX_QPATH];
	int				mFXID;

	struct SGoreEffectType	*mNext;
} TGoreEffectType;

typedef struct SGorePieceType
{
	char			mName[MAX_QPATH];
	char			mBolt[MAX_QPATH];

	TGhoul2			mG2Model;
	qhandle_t		mNormalModel;

	struct SGorePieceType	*mNext;
} TGorePieceType;

typedef struct SGoreEffect
{
	char			mName[MAX_QPATH];
	char			mBolt[MAX_QPATH];

	struct SGoreEffect		*mNext;
} TGoreEffect;

typedef struct SGoreBoltOn
{
	char			mName[MAX_QPATH];
	char			mBolt[MAX_QPATH];

	struct SGoreBoltOn		*mNext;
} TGoreBoltOn;

typedef struct SGoreChunk
{
	char			mRoot[MAX_QPATH];
	char			mBone[MAX_QPATH];
	char			*mSurfacesOn;
	char			*mChildrenOff;
	float			mMinForce;
	float			mMaxForce;

	struct SGoreChunk		*mNext;
} TGoreChunk;

typedef struct SGoreArea
{
	char			mLocation[MAX_QPATH];
	char			*mSurfacesOff;
	char			*mSurfacesOn;
	char			*mBoltsOff;
	char			*mChildren;
	unsigned		mFlags;

	struct SGoreEffect		*mFX;
	struct SGoreBoltOn		*mBoltOns;
	struct SGoreChunk		*mChunks;

	struct SGoreArea		*mNext;
} TGoreArea;











static TGoreEffectType	*GoreEffectTypes;
static TGorePieceType	*GorePieceTypes;
static TGoreArea		*GoreAreas;
static TGoreInfo		GoreInfo[GORE_SIDE_MAX] = 
{
	{ "right", "r" },
	{ "left", "l" }
};

static TGoreLocation	GoreLocations[] = 
{
	{ "none",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},

	{ "foot",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "foot",		GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},
	{ "leg_upper",	GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "leg_upper",	GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},
	{ "leg_lower",	GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "leg_lower",	GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},
	
	{ "hand",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "hand",		GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},
	{ "arm_lower",	GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "arm_lower",	GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},

	{ "head",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "torso",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	
	{ "torso",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "torso",		GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},
	{ "torso",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	//{ "torso",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	{ "arm_upper",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},
	//{ "torso",		GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},
	{ "arm_upper",		GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},

	{ "torso",		GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},

	{ "",			GORE_SIDE_LEFT,		GORE_SIDE_RIGHT	},

	// DEBUG ONE
	{ "torso",		GORE_SIDE_RIGHT,	GORE_SIDE_LEFT	},

/*
	"hand_right",
	"arm_lower_right",
	"torso_right",
	"arm_upper_right",
	"leg_lower_right",
	"leg_upper_right",
	"hip_right",
	"head_back_lower_right",
	"head_back_upper_right",
	"head_front_lower_right",
	"head_front_mid_right",
	"head_front_upper_right",
	"head_side_right",
	"head_right",
*/
};














static const char *CreateFinalName(const char *Input, EGoreSide *Primary, EGoreSide *Opposite, qboolean SwapIfOpposite)
{
	static char	output[256];
	char		*outputPos;
	const char	*replace = "";
	EGoreSide	use = GORE_SIDE_RIGHT;
	qboolean	doSwap = qfalse;
	EGoreSide	save;
	const char	*origInput = Input;

	outputPos = output;
	while(*Input)
	{
		if ((*Input) == '<')
		{
			Input++;
			if ((*Input) == 'P')
			{	// default to primary
				use = *Primary;
			}
			else if ((*Input) == 'O')
			{	// use opposite
				use = *Opposite;
				doSwap = qtrue;
			}
			else
			{
				Com_Error(ERR_DROP, "CreateFinalName: bad input string: %s", origInput);
				break;
			}

			Input++;
			if ((*Input) == 'L')
			{	// default to long name
				replace = GoreInfo[use].mLongName;
			}
			else if ((*Input) == 'S')
			{	// use short name
				replace = GoreInfo[use].mShortName;
			}
			else
			{
				Com_Error(ERR_DROP, "CreateFinalName: bad input string: %s", origInput);
				break;
			}

			strcpy(outputPos, replace);
			outputPos += strlen(replace);

			Input++;

			if ((*Input) != '>')
			{
				Com_Error(ERR_DROP, "CreateFinalName: bad input string: %s", origInput);
				break;
			}
		}
		else
		{
			*outputPos++ = *Input;
		}

		Input++;
	}

	*outputPos = 0;
	if (SwapIfOpposite && doSwap)
	{
		save = *Primary;
		*Primary = *Opposite;
		*Opposite = save;
	}

	return output;
}

static TGoreArea *FindGoreZone(const char *Location, EGoreSide Primary, EGoreSide Opposite)
{
	TGoreArea		*gore = GoreAreas;

	while(gore)
	{
		if (Q_stricmp(CreateFinalName(gore->mLocation, &Primary, &Opposite, qfalse), Location) == 0)
		{
			return gore;
		}

		gore = gore->mNext;
	}

	return 0;
}

static TGoreEffectType *FindGoreEffectType(const char *Name)
{
	TGoreEffectType	*effect = GoreEffectTypes;

	while(effect)
	{
		if (Q_stricmp(effect->mName, Name) == 0)
		{
			return effect;
		}

		effect = effect->mNext;
	}

	return 0;
}

static TGorePieceType *FindGorePieceType(const char *Name)
{
	TGorePieceType	*piece = GorePieceTypes;

	while(piece)
	{
		if (Q_stricmp(piece->mName, Name) == 0)
		{
			return piece;
		}

		piece = piece->mNext;
	}

	return 0;
}

static void CG_ParseGoreEffect(TGPGroup group)
{
	TGoreEffectType	*effect;
	char			file[256];

	effect = (TGoreEffectType *)AllocGorePool(sizeof(*effect));
	memset(effect, 0, sizeof(*effect));

	effect->mNext = GoreEffectTypes;
	GoreEffectTypes = effect;

	trap_GPG_FindPairValue(group, "Name", "", effect->mName);
	trap_GPG_FindPairValue(group, "File", "", file);
	effect->mFXID = trap_FX_RegisterEffect(file);
}

static void CG_ParseGorePiece(TGPGroup group)
{
	TGorePieceType	*piece;
	char			file[256];

	piece = (TGorePieceType *)AllocGorePool(sizeof(*piece));
	memset(piece, 0, sizeof(*piece));

	piece->mNext = GorePieceTypes;
	GorePieceTypes = piece;

	trap_GPG_FindPairValue(group, "Name", "", piece->mName);
	trap_GPG_FindPairValue(group, "Bolt", "", piece->mBolt);
	trap_GPG_FindPairValue(group, "Model", "", file);

	if (trap_G2API_InitGhoul2Model(&piece->mG2Model, file, 0, 0, 0, 0, 0) == -1)
	{	// wasn't a g2 model, so try a regular one
		piece->mNormalModel = trap_R_RegisterModel(file);
	}
}

static TGoreEffect *CG_ParseFX(TGPGroup group)
{
	TGoreEffect		*effect;

	effect = (TGoreEffect *)AllocGorePool(sizeof(*effect));
	memset(effect, 0, sizeof(*effect));

	trap_GPG_FindPairValue(group, "Name", "", effect->mName);
	trap_GPG_FindPairValue(group, "Bolt", "", effect->mBolt);

	return effect;
}

static TGoreBoltOn *CG_ParseBoltOn(TGPGroup group)
{
	TGoreBoltOn		*bolt;

	bolt = (TGoreBoltOn *)AllocGorePool(sizeof(*bolt));
	memset(bolt, 0, sizeof(*bolt));

	trap_GPG_FindPairValue(group, "Name", "", bolt->mName);
	trap_GPG_FindPairValue(group, "Bolt", "", bolt->mBolt);

	return bolt;
}

static TGoreChunk *CG_ParseChunk(TGPGroup group)
{
	TGoreChunk		*chunk;
	char			temp[256];

	chunk = (TGoreChunk *)AllocGorePool(sizeof(*chunk));
	memset(chunk, 0, sizeof(*chunk));

	trap_GPG_FindPairValue(group, "Root", "", chunk->mRoot);
	trap_GPG_FindPairValue(group, "Bone", "", chunk->mBone);
	chunk->mSurfacesOn = AllocMultiString(trap_GPG_FindPair(group, "Surfaces_On"));
	chunk->mChildrenOff = AllocMultiString(trap_GPG_FindPair(group, "Children_Off"));

	trap_GPG_FindPairValue(group, "MinForce", "40", temp);
	chunk->mMinForce = atof(temp);
	trap_GPG_FindPairValue(group, "MaxForce", "80", temp);
	chunk->mMaxForce = atof(temp);

	return chunk;
}

static void CG_ParseGoreArea(TGPGroup group)
{
	TGoreArea		*gore;
	TGoreEffect		*effect;
	TGoreBoltOn		*bolt;
	TGoreChunk		*chunk;
	TGPValue		flags, value;
	TGPGroup		sub;
	char			name[256];

	gore = (TGoreArea *)AllocGorePool(sizeof(*gore));
	memset(gore, 0, sizeof(*gore));

	gore->mNext = GoreAreas;
	GoreAreas = gore;

	trap_GPG_FindPairValue(group, "Location", "", gore->mLocation);

	gore->mSurfacesOff = AllocMultiString(trap_GPG_FindPair(group, "Surfaces_Off"));
	gore->mSurfacesOn = AllocMultiString(trap_GPG_FindPair(group, "Surfaces_On"));
	gore->mBoltsOff = AllocMultiString(trap_GPG_FindPair(group, "Bolts_Off"));
	gore->mChildren = AllocMultiString(trap_GPG_FindPair(group, "Children"));

	flags = trap_GPG_FindPair(group, "Flags");
	if (flags)
	{
		value = trap_GPV_GetList(flags);
		while(value)
		{
			trap_GPV_GetName(value, name);
			if (Q_stricmp(name, "NoChildSurfacesOn") == 0)
			{
				gore->mFlags |= GORE_NO_CHILD_SURFACES_ON;
			}
			else if (Q_stricmp(name, "NoChildFX") == 0)
			{
				gore->mFlags |= GORE_NO_CHILD_FX;
			}
			else if (Q_stricmp(name, "NoChildChunks") == 0)
			{
				gore->mFlags |= GORE_NO_CHILD_CHUNKS;
			}			
			else if (Q_stricmp(name, "NoChildBoltOns") == 0)
			{
				gore->mFlags |= GORE_NO_CHILD_BOLTONS;
			}			
			
			value = trap_GPV_GetNext(value);
		}
	}

	sub = trap_GPG_GetSubGroups(group);
	while(sub)
	{
		trap_GPG_GetName(sub, name);
		if (Q_stricmp(name, "FX") == 0)
		{
			effect = CG_ParseFX(sub);
			effect->mNext = gore->mFX;
			gore->mFX = effect;
		}
		else if (Q_stricmp(name, "Chunk") == 0)
		{
			chunk = CG_ParseChunk(sub);
			chunk->mNext = gore->mChunks;
			gore->mChunks = chunk;
		}
		else if (Q_stricmp(name, "BoltOn") == 0)
		{
			bolt = CG_ParseBoltOn(sub);
			bolt->mNext = gore->mBoltOns;
			gore->mBoltOns = bolt;
		}

		sub = trap_GPG_GetNext(sub);
	}
}

qboolean CG_ParseGore(void)
{
	TGenericParser2	GP2;
	TGPGroup		topGroup, topSubs;
	char			name[256];

	GP2 = trap_GP_ParseFile("ext_data/sof2.gore", qtrue, qfalse);
	if (!GP2)
	{
		return qfalse;
	}

	gorePoolSize = 0;
	GoreEffectTypes = 0;
	GorePieceTypes = 0;
	GoreAreas = 0;

	topGroup = trap_GP_GetBaseParseGroup(GP2);
	topSubs = trap_GPG_GetSubGroups(topGroup);
	while(topSubs)
	{
		trap_GPG_GetName(topSubs, name);
		if (Q_stricmp(name, "gore_area") == 0)
		{
			CG_ParseGoreArea(topSubs);
		}
		else if (Q_stricmp(name, "gore_effect") == 0)
		{
			CG_ParseGoreEffect(topSubs);
		}
		else if (Q_stricmp(name, "gore_piece") == 0)
		{
			CG_ParseGorePiece(topSubs);
		}

		topSubs = trap_GPG_GetNext(topSubs);
	}

	trap_GP_Delete(&GP2);

	return qtrue;
}

















static void CG_ProcessSurfaceList(void *model, char *surfaceList, int flags,
								  EGoreSide Primary, EGoreSide Opposite)
{
	while(surfaceList && surfaceList[0])
	{
		if (!trap_G2API_SetSurfaceOnOff(model, 0, CreateFinalName(surfaceList, &Primary, &Opposite, qfalse), flags))
		{

#ifdef _DEBUG
//			Com_Printf("Missing surface '%s'\n", surfaceList);
#endif
		}
		surfaceList += strlen(surfaceList) + 1;
	}
}

static void CG_ProcessBoltList(void *model, char *boltList, EGoreSide Primary, EGoreSide Opposite)
{
	int		numModels;
	int		boltIndex;
	int		i;

	while(boltList && boltList[0])
	{
		numModels = trap_G2API_GetNumModels(model);
		boltIndex = trap_G2API_FindBoltIndex(model, 0, CreateFinalName(boltList, &Primary, &Opposite, qfalse));
		if (boltIndex != -1)
		{
			for(i=1;i<numModels;i++)
			{
				if (trap_G2API_GetBoltIndex(model, i) == boltIndex)
				{
					trap_G2API_DetachG2Model(model, i);
					trap_G2API_RemoveGhoul2Model(&model, i);
					numModels--;
					i--;
				}
			}
		}

		boltList += strlen(boltList) + 1;
	}
}

static void CG_ProcessChunkChild(void *Ghoul2, char *Name,
								 EGoreSide Primary, EGoreSide Opposite)
{
	TGoreArea		*gore;
	char			*child;
	char			finalName[256];

	strcpy(finalName, CreateFinalName(Name, &Primary, &Opposite, qtrue));
	gore = FindGoreZone(finalName, Primary, Opposite);
	if (!gore)
	{
		return;
	}

	CG_ProcessSurfaceList(Ghoul2, gore->mSurfacesOff, G2SURFACEFLAG_OFF, Primary, Opposite);

	child = gore->mChildren;
	while(child && child[0])
	{
		CG_ProcessChunkChild(Ghoul2, child, Primary, Opposite);

		child += strlen(child) + 1;
	}
}

static void CG_ProcessChunk(int clientNum, centity_t *cent, TGoreChunk *chunk, vec3_t Direction,
							EGoreSide Primary, EGoreSide Opposite)
{
	localEntity_t	*le;
	refEntity_t		*re;
	int				bolt;
	char			*child;
	animation_t		*anim;
	float			animSpeed;
	int				flags=BONE_ANIM_OVERRIDE_FREEZE;
	clientInfo_t	*ci;
	mdxaBone_t		matrix;
	qboolean		boltMatrixOK = qfalse;;

	le = CG_AllocLocalEntity();
	re = &le->refEntity;

	le->leType = LE_GIB;
	le->startTime = cg.time;
	le->endTime = le->startTime + BODY_SINK_DELAY + BODY_SINK_TIME;
	le->leFlags = LEF_TUMBLE;
	le->bounceFactor = 0.2f;
	re->radius = 50;
	re->renderfx = RF_MINLIGHT;

	VectorSet ( re->modelScale, 1, 1, 1 );

	AxisCopy( axisDefault, re->axis );

	le->pos.trType = TR_GRAVITY;
	VectorMA ( vec3_origin, irand ( chunk->mMinForce * 2, chunk->mMaxForce * 2), Direction, le->pos.trDelta );
	le->pos.trDelta[2] = flrand ( 100, 150 );
	le->pos.trTime = cg.time;

	le->angles.trType = TR_LINEAR_STOP;
	VectorClear(le->angles.trBase);
	le->angles.trBase[YAW] = crandom() * 15;
	le->angles.trDelta[0] = 0.0; // crandom();
	le->angles.trDelta[YAW] = crandom() * 15 - 7;
	le->angles.trDelta[2] = 0.0; // crandom();
	le->angles.trDuration = BODY_SINK_DELAY + BODY_SINK_TIME;
	le->angles.trTime = cg.time;

	le->zOffset = 26.0;

	// ghoul stuff to do limbs
	if (!cent->ghoul2)
	{
		Com_Error(ERR_DROP, "CG_ProcessChunk invalid g2 pointer for client %d\n", clientNum);
	}

	trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &re->ghoul2);
	if (!re->ghoul2)
	{	// whoa, that surface caused our model to go away???
		CG_FreeLocalEntity(le);
		return;
	}

	trap_G2API_SetRootSurface(&re->ghoul2, 0, CreateFinalName(chunk->mRoot, &Primary, &Opposite, qfalse));

	bolt = trap_G2API_AddBolt(cent->ghoul2, 0, CreateFinalName(chunk->mBone, &Primary, &Opposite, qfalse));
	if (bolt != -1)
	{
		boltMatrixOK = trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->lerpAngles, 
			cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
	}

	if (bolt == -1 || !boltMatrixOK)
	{
		return;
	}

	matrix.matrix[0][3] = cent->lerpOrigin[0];
	matrix.matrix[1][3] = cent->lerpOrigin[1];
	matrix.matrix[2][3] = cent->lerpOrigin[2];

	le->pos.trBase[0] = re->origin[0] = matrix.matrix[0][3];
	le->pos.trBase[1] = re->origin[1] = matrix.matrix[1][3];
	le->pos.trBase[2] = re->origin[2] = matrix.matrix[2][3];

	bolt = trap_G2API_AddBolt(re->ghoul2, 0, CreateFinalName(chunk->mBone, &Primary, &Opposite, qfalse));
	trap_G2API_SetNewOrigin(re->ghoul2, 0, bolt);
	
	ci = &cgs.clientinfo[clientNum];
	anim = &ci->animations[cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT];
	animSpeed = 50.0f / anim->frameLerp;

	trap_G2API_SetBoneAnim(re->ghoul2, 0, "model_root", anim->firstFrame + anim->numFrames - 1, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 0);
	trap_G2API_SetBoneAnim(re->ghoul2, 0, "lower_lumbar", anim->firstFrame + anim->numFrames - 1, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 0);

	CG_ProcessSurfaceList(re->ghoul2, chunk->mSurfacesOn, 0, Primary, Opposite);

	child = chunk->mChildrenOff;
	while(child && child[0])
	{
		CG_ProcessChunkChild(re->ghoul2, child, Primary, Opposite);

		child += strlen(child) + 1;
	}
}

static void CG_ProcessBoltOn(int clientNum, centity_t *cent, TGoreBoltOn *bolton,
							EGoreSide Primary, EGoreSide Opposite)
{
	TGorePieceType	*piece = FindGorePieceType(bolton->mName);
	const char		*boltPosition;
	int				pieceIndex;
	int				boltIndex;

	if (!piece)
	{
		return;
	}

	if (piece->mG2Model)
	{
		boltPosition = bolton->mBolt;
		if (!boltPosition[0])
		{
			boltPosition = piece->mBolt;
		}

		pieceIndex = trap_G2API_CopySpecificGhoul2Model(piece->mG2Model, 0, cent->ghoul2, -1);
		if (pieceIndex != -1)
		{
			boltIndex = trap_G2API_AddBolt(cent->ghoul2, 0, CreateFinalName(boltPosition, &Primary, &Opposite, qfalse));
			if (boltIndex != -1)
			{
				trap_G2API_AttachG2Model(cent->ghoul2, pieceIndex, cent->ghoul2, boltIndex, 0);
			}
		}
	}
}

static void CG_ProcessGore(int clientNum, centity_t *cent, const char *Location, unsigned Flags,
						   vec3_t Direction, EGoreSide Primary, EGoreSide Opposite)
{
	TGoreArea		*gore;
	TGoreEffect		*fx;
	TGoreChunk		*chunk;
	TGoreBoltOn		*bolton;
	char			*children;
	int				bolt;
	mdxaBone_t		matrix;
	qboolean		boltMatrixOK;
	vec3_t			origin;
	TGoreEffectType *effect;
	char			finalName[256];

	strcpy(finalName, CreateFinalName(Location, &Primary, &Opposite, qtrue));
	gore = FindGoreZone(finalName, Primary, Opposite);
	if (!gore)
	{
		return;
	}

	if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_FX) )
	{
		fx = gore->mFX;
		while(fx)
		{
			effect = FindGoreEffectType(fx->mName);
			if (effect)
			{
				bolt = trap_G2API_AddBolt(cent->ghoul2, 0, CreateFinalName(fx->mBolt, &Primary, &Opposite, qfalse));
				if (bolt != -1)
				{
					vec3_t axis[3];
					int boltInfo;

					boltMatrixOK = trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->lerpAngles, 
											cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);					
					axis[0][0] = matrix.matrix[0][0];
					axis[0][1] = matrix.matrix[1][0];
					axis[0][2] = matrix.matrix[2][0];
					axis[1][0] = matrix.matrix[0][1];
					axis[1][1] = matrix.matrix[1][1];
					axis[1][2] = matrix.matrix[2][1];
					axis[2][0] = matrix.matrix[0][2];
					axis[2][1] = matrix.matrix[1][2];
					axis[2][2] = matrix.matrix[2][2];
 					origin[0] = matrix.matrix[0][3];
 					origin[1] = matrix.matrix[1][3];
 					origin[2] = matrix.matrix[2][3];


					boltInfo    =  (( 0/*modelnum*/ & MODEL_AND  ) << MODEL_SHIFT  ); 
					boltInfo    |= (( bolt  & BOLT_AND   ) << BOLT_SHIFT   );
					boltInfo    |= (( cent->currentState.number   & ENTITY_AND ) << ENTITY_SHIFT );

					trap_FX_PlayEntityEffectID( effect->mFXID, origin, axis, boltInfo, -1, -1, -1);
/*					boltMatrixOK = trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->lerpAngles, 
						cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale);
					if (boltMatrixOK)
					{
 						origin[0] = matrix.matrix[0][3];
 						origin[1] = matrix.matrix[1][3];
 						origin[2] = matrix.matrix[2][3];
						trap_FX_PlayEffectID(effect->mFXID, origin, Direction, -1, -1 );
					}
*/				}
			}

			fx = fx->mNext;
		}
	}

	if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_CHUNKS) )
	{
		chunk = gore->mChunks;
		while(chunk)
		{
			CG_ProcessChunk(clientNum, cent, chunk, Direction, Primary, Opposite);

			chunk = chunk->mNext;
		}
	}

	if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_BOLTONS) )
	{
		bolton = gore->mBoltOns;
		while(bolton)
		{
			CG_ProcessBoltOn(clientNum, cent, bolton, Primary, Opposite);

			bolton = bolton->mNext;
		}
	}

	CG_ProcessSurfaceList(cent->ghoul2, gore->mSurfacesOff, G2SURFACEFLAG_OFF, Primary, Opposite);
	if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_SURFACES_ON) )
	{	// children with no gore from the parents shouldn't do this
		CG_ProcessSurfaceList(cent->ghoul2, gore->mSurfacesOn, 0, Primary, Opposite);
	}
	CG_ProcessBoltList(cent->ghoul2, gore->mBoltsOff, Primary, Opposite);

	children = gore->mChildren;
	while(children && children[0])
	{
		CG_ProcessGore(clientNum, cent, children, gore->mFlags | Flags | GORE_CHILD, Direction, Primary, Opposite);
		children += strlen(children) + 1;
	}
}


void CG_ApplyGore(int clientNum, centity_t *cent, int hitLocation, vec3_t Direction)
{
	TGoreLocation	*Location = 0;
	const char		*dg, *token;
	char			area[256];
	EGoreSide		side;
	int				i;

	// Gore locked?
	if ( cg_lockSever.integer )
	{
		return;
	}

	dg = cg_DebugGore.string;
	if (dg[0])
	{
		strcpy(area, COM_Parse(&dg));
		token = COM_Parse(&dg);
		for(side=0;side<GORE_SIDE_MAX;side++)
		{
			if (Q_stricmp(GoreInfo[side].mLongName, token) == 0 ||
				Q_stricmp(GoreInfo[side].mShortName, token) == 0)
			{
				break;
			}
		}
		for(i=0;i<HL_DEBUG;i++)
		{
			if (Q_stricmp(GoreLocations[i].mPublicName, area) == 0 &&
				GoreLocations[i].mPrimarySide == side)
			{
				Location = &GoreLocations[i];
				break;
			}
		}
	}

	if (!Location)
	{
		if (hitLocation < 0 || hitLocation >= (sizeof(GoreLocations) / sizeof(struct SGoreLocation)))
		{
			Com_Error( ERR_DROP, "CG_ApplyGore: invalid hit location %d\n", hitLocation);
			return;
		}
		Location = &GoreLocations[hitLocation];	
	}
	else
	{
		// just so that I don't have to keep restarting the game to change the file
		CG_ParseGore();

		Com_Printf ( "GORE:  Applying gore to location '%s'\n", Location->mPublicName );
	}

	CG_ProcessGore(clientNum, cent, Location->mPublicName, 0, Direction, Location->mPrimarySide, Location->mOppositeSide);
}

void CG_ShutdownGore(void)
{
	TGorePieceType	*piece = GorePieceTypes;

	while(piece)
	{
		if (piece->mG2Model)
		{
			trap_G2API_CleanGhoul2Models(&piece->mG2Model);
		}
		piece = piece->mNext;
	}

}



