Menu

[r6343]: / trunk / quakec / csqctest / src / cs / q3playerm.qc  Maximize  Restore  History

Download this file

1261 lines (1090 with data), 37.0 kB

//This file provides support for Q3-compatible player models.

/*
	This module provides:
		bool(string newmodelskin) Anim_SetModel
			This function applies the named model (eg: "sarge" or "sarge/red") to self.
			Returns false on failure. If it fails, the entity will be untouched.
		void() Anim_Draw
			This function adds self's segmented models to the scene.
			This is intended to be called from inside a predraw function.
		void() Anim_UnsetModel
			This frees everything assosiated with self, but Does not flush the animation file.
		entity() Anim_DupModel
			Duplicates the animation state/model from self entity to a newly spawned entity.
			This is expected to be used for bodyqueues.
		float(string fname) Anim_ReadAnimationFile
			An explicit call is not required, except as perhaps a precache.
			fname is eg: "sarge", but NOT "sarge/red".
			Returns -1 on failure.
			Fails if list is full or animation.cfg is not found. Does not fail if a model is not found.
		float() Anim_GetGender
			Returns which gender the 'self' player model is.
		float(string skinname) Anim_GetHeadModelIndex
			Returns a modelindex assosiated with the given model/skin name.
			Intended for the hud.
		float(string skinname) Anim_GetHeadSkinNumber
			Returns a skin number for use with the modelindex returned by Anim_GetHeadModelIndex to give the correct model/skin for the given model/skin parameter.
			Intended for the hud.
		vector(string skinname) Anim_GetHeadOffset
			Retrieves the q3 head offset for displaying the head in a nice neat box on the hud.

	if only I were allowed to use pointers... these three head functions would be a single more efficient function

*/

#ifdef MD3PMODELS

//these are the animation sequence names used in quake3.
enum {
	BOTH_DEATH1,
	BOTH_DEAD1,
	BOTH_DEATH2,
	BOTH_DEAD2,
	BOTH_DEATH3,
	BOTH_DEAD3,

	TORSO_GESTURE,

	TORSO_ATTACK,
	TORSO_ATTACK2,

	TORSO_DROP,
	TORSO_RAISE,

	TORSO_STAND,
	TORSO_STAND2,

	LEGS_WALKCR,
	LEGS_WALK,
	LEGS_RUN,
	LEGS_BACK,
	LEGS_SWIM,

	LEGS_JUMP,
	LEGS_LAND,

	LEGS_JUMPB,
	LEGS_LANDB,

	LEGS_IDLE,
	LEGS_IDLECR,
	LEGS_TURN,

	NUMANIMS
};


//we use qc arrays to store all the information. this results in a lot of prog space used.
#define MAXMODELS 50	//if progs size becomes an issue, reduce this.

//a size optimisation.
#define anim_firstframe A
#define anim_numframes B
#define anim_loopingframes C
#define anum_framespersecond D

//a q3 animation.cfg file contains 4 items per sequence.
nosave float anim_firstframe[NUMANIMS*MAXMODELS];	//first frame in the sequence
nosave float anim_numframes[NUMANIMS*MAXMODELS];	//number of frames in this sequence.
nosave float anim_loopingframes[NUMANIMS*MAXMODELS];	//number of frames to play while looping (jumps to total-looping)
nosave float anum_framespersecond[NUMANIMS*MAXMODELS];//number of frames to play per second.

nosave float anim_gender[MAXMODELS];	//one of the SEX_ enum values.
nosave string anim_name[MAXMODELS];		//names the (skinless) player model, so they can be cached and shared.
nosave float anim_headmodel[MAXMODELS];
nosave vector anim_headoffset[MAXMODELS];

//note: q3 assumes male.
//we assume male too, due to the player model that is typically used if we have no q3 models.
#define GENDER_DEFAULT GENDER_MALE


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//This block of code is to replace builtins which may be missing in your engine of choice.
//Its all generic maths/tag stuff, so consider it just noise :)
//this doesn't affect how the model is built.

#ifdef WORKINDP
vector getlerpedtaginfo(entity ent, float tagnum)
{
	//personally I consider it a bug that this function is needed.
	//this function exactly duplicates the gettaginfo builtin
	//but ensures that interpolation based on frame2 happens.
	//this matches how the renderer will lerp frames.

	//at the time of writing, this is needed for DP to be smooth.
	//fte does not support gettaginfo at all (I've been lazy due to the heirachical nature).
	//this is called by rotatevectorsbytag_GTI, so it not required in fte as FTE has a working rotatevectorsbytag instead.

	float frame2ness = ent.lerpfrac;
	float frame1ness = 1-frame2ness;
	float f1=ent.frame;
	float f2=ent.frame2;
//it doesn't matter what the ent's lerpfrac currently is.
	vector f1o, f1f, f1r, f1u;
	vector f2o, f2f, f2r, f2u;
	vector v_origin;

	//make sure both frames are set, in case the builtin is fixed
	ent.frame = f1;
	ent.frame2 = f1;
	f1o = gettaginfo(ent, tagnum);
	f1r=v_right;f1f=v_forward;f1u=v_up;

	ent.frame = f2;
	ent.frame2 = f2;
	v_origin = gettaginfo(ent, tagnum);

	//restore the entity
	ent.frame = f1;
	ent.frame2 = f2;

	//lerp the current frame2 with the cached frame1 values
	v_forward = f1f*frame1ness+v_forward*frame2ness;
	v_right = f1r*frame1ness+v_right*frame2ness;
	v_up = f1u*frame1ness+v_up*frame2ness;
	v_origin = f1o*frame1ness+v_origin*frame2ness;

	return v_origin;
}

#define rotatevectorsbytag rotatevectorsbytag_GTI
vector rotatevectorsbytag_GTI(entity ent, float tagnum)
{
	vector saveang=ent.angles;
	vector saveorg=ent.origin;

	vector oldx=v_forward, oldy=('0 0 0'-v_right), oldz=v_up;
	vector oldo=ent.origin;

	ent.angles = '0 0 0';
	ent.origin = '0 0 0';
	vector ango=getlerpedtaginfo(ent, tagnum);
	ent.angles = saveang;
	ent.origin = saveorg;
//note: v_right is actually left, in tags.
	vector angx=v_forward, angy=v_right, angz=v_up;

	angx = normalize(angx);
	angy = normalize(angy);
	angz = normalize(angz);

	//multiply out the tag matrix with the previous matrix.
	v_forward_x = angx_x*oldx_x + angx_y*oldy_x + angx_z*oldz_x;
	v_forward_y = angx_x*oldx_y + angx_y*oldy_y + angx_z*oldz_y;
	v_forward_z = angx_x*oldx_z + angx_y*oldy_z + angx_z*oldz_z;
	v_right_x   = angy_x*oldx_x + angy_y*oldy_x + angy_z*oldz_x;
	v_right_y   = angy_x*oldx_y + angy_y*oldy_y + angy_z*oldz_y;
	v_right_z   = angy_x*oldx_z + angy_y*oldy_z + angy_z*oldz_z;
	v_up_x      = angz_x*oldx_x + angz_y*oldy_x + angz_z*oldz_x;
	v_up_y      = angz_x*oldx_y + angz_y*oldy_y + angz_z*oldz_y;
	v_up_z      = angz_x*oldx_z + angz_y*oldy_z + angz_z*oldz_z;

	v_right = '0 0 0'-v_right;

	//transform the tag's origin
	saveorg = oldo;
	saveorg_x += ango_x * oldx_x;
	saveorg_y += ango_x * oldx_y;
	saveorg_z += ango_x * oldx_z;
	saveorg_x += ango_y * oldy_x;
	saveorg_y += ango_y * oldy_y;
	saveorg_z += ango_y * oldy_z;
	saveorg_x += ango_z * oldz_x;
	saveorg_y += ango_z * oldz_y;
	saveorg_z += ango_z * oldz_z;
	return saveorg;
}
#define rotatevectorsbyangle rotatevectorsbyangle_QC
void rotatevectorsbyangle_QC(vector angle)
{
	vector oldx=v_forward, oldy='0 0 0'-v_right, oldz=v_up;
	angle_x = -angle_x;
	makevectors(angle);
	vector angx=v_forward, angy='0 0 0'-v_right, angz=v_up;

	v_forward_x = angx_x*oldx_x + angx_y*oldy_x + angx_z*oldz_x;
	v_forward_y = angx_x*oldx_y + angx_y*oldy_y + angx_z*oldz_y;
	v_forward_z = angx_x*oldx_z + angx_y*oldy_z + angx_z*oldz_z;
	v_right_x   = angy_x*oldx_x + angy_y*oldy_x + angy_z*oldz_x;
	v_right_y   = angy_x*oldx_y + angy_y*oldy_y + angy_z*oldz_y;
	v_right_z   = angy_x*oldx_z + angy_y*oldy_z + angy_z*oldz_z;
	v_up_x      = angz_x*oldx_x + angz_y*oldy_x + angz_z*oldz_x;
	v_up_y      = angz_x*oldx_y + angz_y*oldy_y + angz_z*oldz_y;
	v_up_z      = angz_x*oldx_z + angz_y*oldy_z + angz_z*oldz_z;

	v_right = '0 0 0'-v_right;
}
#define RotateVectorsByVectors RotateVectorsByVectorsDP

//
#define skinforname(i,n) 0
#endif

//end noisy maths.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//a utility function required by sexedsound so we don't try playing missing sounds (its common for male models to be missing some)
float(string str) fileexists =
{
	//we use searches because its more likly to be cached than reading a 200kb file from the disk every time.
	local float search;
	search = search_begin(str, false, true);
	if (search < 0)
	{
		return false;
	}
	search_end(search);
	return true;
};

//plays a CHAN_VOICE sound around the given entity.
void(entity ent, string soundname) sexedsound =
{
	string str, full;
	if (self == player_local)
		if (CVARF(cg_noselfsounds))
			return;	//option to disable sounds from local player

	str = strcat("player/", anim_name[self.modelnum], "/", soundname);
	full = strcat("sound/", str);
	if (fileexists(full))
	{
		precache_sound(str);
		sound(self, CHAN_VOICE, str, 1, 1, 95 + random()*10);
	}
	else
	{
		str = strcat("player/sarge/", soundname);	// :(
		precache_sound(str);
		sound(self, CHAN_VOICE, str, 1, 1, 95 + random()*10);
	}
};


//our player's frames changed to some new sequence.
//our animation function will animate acordingly.

static void(float anum) ForceToAnim =
{
	if (anum <= TORSO_STAND2)
	{
		self.torsoent.animnum = anum;
		self.torsoent.framechangetime = time;
	}
	if (anum <= BOTH_DEAD3 || anum >= LEGS_WALKCR)
	{
		self.legsent.animnum = anum;
		self.legsent.framechangetime = time;
	}
};

//this function is per-segment.
//it updates the frame/frame2/lerpfrac values and loops/caps the animation.
//returns true if the end of the current animation was reached.
float(entity ent) animate =
{
//true if looped
	float anum;
	float ft;
	float fnum;
	float numframes;
	float fps;
	float loopingframes;
	float ret;

	anum = (ent.modelnum * NUMANIMS) + ent.animnum;

	ft = time - ent.framechangetime;
	numframes = anim_numframes[anum];
	fps = anum_framespersecond[anum];

	ft *= fps;
	fnum = floor(ft);
	ft = ft - fnum;	//time into the frame.
	ret = fnum >= numframes;
	if (ret)
	{
		loopingframes = anim_loopingframes[anum];

		if (loopingframes)
		{
			fnum -= numframes - loopingframes;
			fnum = fnum - floor(fnum/loopingframes)*loopingframes;
			fnum += numframes - loopingframes;
		}
		else
		{	//stop at the end of it.
			fnum = numframes-1;
			ft = 1;
		}

		if (numframes == 1)
			ent.frame2 = ent.frame;
	}
	fnum += anim_firstframe[anum];

	if (ent.frame2 == -1)
	{	//special flag to make it snap for the initial frame
		ent.frame = fnum;
		ent.frame2 = fnum;
	}
	else if (ent.frame != fnum)
	{
		ent.frame2 = ent.frame;
		ent.frame = fnum;
	}
	ent.lerpfrac = 1-ft;
	if (ent.lerpfrac<=0)
		ent.frame2 = ent.frame;

	return ret;
};

.float wasinair;
//this function checks to see if the player is jumping/flying/dead/moving, and just updates the legs to match the current events and time.
void() LegsUpdateAnim =
{
	float inair;

	tracebox(self.origin, self.mins, self.maxs, self.origin-'0 0 2', FALSE, self);

	if (trace_fraction == 1)
		inair = self.velocity_z;
	else
		inair = 0;

	if (self.legsent.animnum > BOTH_DEAD3)
	if ((self.wasinair!=0) != (inair!=0))
	{
		if (trace_fraction == 1)	//in air
		{
			if (self.velocity_z > 5)
			{
				makevectors(self.angles);
				if (v_forward * self.velocity >= 0)
					ForceToAnim(LEGS_JUMP);
				else
					ForceToAnim(LEGS_JUMPB);
			}
		}
		else
		{
			if (inair < -5 || self.wasinair < -5)
			{
				if (self.animnum == LEGS_JUMPB)
					ForceToAnim(LEGS_LANDB);
				else
					ForceToAnim(LEGS_LAND);
			}
		}
		self.wasinair = inair;
	}

	if (vlen(self.velocity) > 0.1)
	{
		if (self.legsent.animnum == LEGS_IDLE)
		{
			if (self.velocity * v_forward > 0)
				ForceToAnim(LEGS_RUN);
			else
				ForceToAnim(LEGS_BACK);
		}
	}
	else
	{
		if (self.legsent.animnum == LEGS_RUN || self.legsent.animnum == LEGS_BACK)
			ForceToAnim(LEGS_IDLE);
	}

	if (animate(self.legsent))
	{
		if (self.legsent.animnum <= BOTH_DEAD3)
		{
			return;	//don't play the idle anim when dead.
		}
		if (self.wasinair)
		{
			return;	//don't change the legs anim whilst flying.
		}

		if (self.velocity_x || self.velocity_y)
		{
			if (self.velocity * v_forward > 0)
				ForceToAnim(LEGS_RUN);
			else
				ForceToAnim(LEGS_BACK);
		}
		else
		{
			ForceToAnim(LEGS_IDLE);
		}

		animate(self.legsent);
	}
};

//updates the torso's animations. basically just shoots/gestures/or otherwise just updates to the current time.
void() TorsoUpdateAnim =
{
	if (animate(self.torsoent))
	{
		playerframe f = self.frame;
		if (f >= playerframe::axdeth1 && f <= playerframe::deathe9)
			return;	//dead.

		if (f == playerframe::nailatt1 || f == playerframe::nailatt2 ||
		    f == playerframe::light1 || f == playerframe::light2)
			ForceToAnim(TORSO_ATTACK);	//these ones loop
		else if (random() < 0.005 && !(self.velocity_x || self.velocity_y) && (chasecam || self != player_local))
		{	//randomly taunt, when standing still, when not first-person (making the sounds is just confusing when first person)
			sexedsound(self, "taunt.wav");
			ForceToAnim(TORSO_GESTURE);
		}
		else if ((f >= playerframe::axrun1 && f <= playerframe::axrun6) ||
			 (f >= playerframe::axstnd1 && f <= playerframe::axstnd12) ||
			 (f >= playerframe::axpain1 && f <= playerframe::axpain6))
			ForceToAnim(TORSO_STAND2);
		else
			ForceToAnim(TORSO_STAND);
		animate(self.legsent);
	}
};

//adds a q3-shell around the model based on which powerups they have active.
//and just basically acts as a wrapper to addentity.
void(entity ent) AddModelWithEffects =
{
	if (self.sveffects & SVE_INVIS)
		ent.forceshader = shaderforname("powerups/invisibility");

	addentity(ent);
	if (self.sveffects & SVE_QUAD)
	{
		ent.fatness = -2;
		ent.forceshader = shaderforname("powerups/quad");
		addentity(ent);
	}
	if (self.sveffects & SVE_GOD)
	{
		ent.fatness = -2;
		ent.forceshader = shaderforname("powerups/regen");
		addentity(ent);
	}
	ent.fatness = 0;
	ent.forceshader = 0;
};

//this function is called inside your base entity's predraw function (which should then return false)
nonstatic void() Anim_Draw =
{
	vector tf, tr, tu;
	vector ang;
	float move;
	string weaponname = 0;

	self.modelindex = 0;	//should never have been anything else.


	if (player_local == self && !chasecam)
	{
		//we still add the local player entity, although only to mirrors.
		self.legsent.renderflags = RF_USEAXIS|RF_EXTERNALMODEL;
		self.torsoent.renderflags = RF_USEAXIS|RF_EXTERNALMODEL;
		self.headent.renderflags = RF_USEAXIS|RF_EXTERNALMODEL;
		self.weaponent.renderflags = RF_USEAXIS|RF_EXTERNALMODEL;
	}
	else
	{
		self.legsent.renderflags = RF_USEAXIS;
		self.torsoent.renderflags = RF_USEAXIS;
		self.headent.renderflags = RF_USEAXIS;
		self.weaponent.renderflags = RF_USEAXIS;
	}

	if (!(self.frame >= playerframe::axdeth1 && self.frame <= playerframe::deathe9))
	{
		//if they're still alive
		//animate the legs a bit so they turn to the player, but not for 1-degree turns. little more realism there.

		ang_y = self.angles_y;
		if (self.velocity_x||self.velocity_y)
		{
			makevectors(ang);
			tf = self.velocity;
			tf_z = 0;
			tf = normalize(tf);
			if (v_forward*tf > -0.01)
				self.legsent.ideal_yaw = vectoyaw(tf);
			else
				self.legsent.ideal_yaw = vectoyaw(tf)+180;
		}
		else
		{
			move = ang_y - self.legsent.ideal_yaw;
			if (move < -180)
				move += 360;
			if (move > 180)
				move -= 360;

			if (move*move > 30*30)
				ForceToAnim(LEGS_TURN);

			if (self.legsent.animnum == LEGS_TURN || self.legsent.animnum == LEGS_JUMP)
				self.legsent.ideal_yaw = ang_y;
		}
		other = self;
		self = self.legsent;
		self.yaw_speed = 5;//180*frametime;

		//clamp the legs to never be more than 90 degrees
		move = other.angles_y-self.angles_y;
		if (move < -180)
			move += 360;
		if (move > 180)
			move -= 360;
		//turn the legs
		if (move*move > 95*95)	//snap
		{
			if (move>0)
				move=20;
			else
				move=-20;
			self.angles_y = other.angles_y - move;
			self = other;
		}
		else
			ChangeYaw();
		self = other;
	}
	

	if (self.frame != self.frame2)
	{
	//see if the player changed to any animations.
		if (self.frame >= playerframe::axdeth1 && self.frame <= playerframe::axdeth8 && self.legsent.animnum != BOTH_DEATH1)
		{
			sexedsound(self, "death1.wav");
			ForceToAnim(BOTH_DEATH1);
		}
		else if (self.frame == playerframe::axdeth9 && self.frame2 != playerframe::axdeth8)
			ForceToAnim(BOTH_DEAD1);	//suddenly appeared with this frame (otherwise we just flow into it)

		else if (self.frame == playerframe::deatha1 && self.frame <= playerframe::deatha10 && self.legsent.animnum != BOTH_DEATH2)
		{
			sexedsound(self, "death2.wav");
			ForceToAnim(BOTH_DEATH2);
		}
		else if (self.frame == playerframe::deatha11 && self.frame2 != playerframe::deatha10)
			ForceToAnim(BOTH_DEAD2);	//suddenly appeared with this frame (otherwise we just flow into it)

		else if (self.frame == playerframe::deathb1 && self.frame <= playerframe::deathb8 && self.legsent.animnum != BOTH_DEATH3)
		{
			sexedsound(self, "death3.wav");
			ForceToAnim(BOTH_DEATH3);
		}
		else if (self.frame == playerframe::deathb9 && self.frame2 != playerframe::deathb8)
			ForceToAnim(BOTH_DEAD3);	//suddenly appeared with this frame (otherwise we just flow into it)

		else if (self.frame == playerframe::deathc1 && self.frame <= playerframe::deathc14 && self.legsent.animnum != BOTH_DEATH1)
		{
			sexedsound(self, "death1.wav");
			ForceToAnim(BOTH_DEATH1);
		}
		else if (self.frame == playerframe::deathc15 && self.frame2 != playerframe::deathc14)
			ForceToAnim(BOTH_DEAD1);	//suddenly appeared with this frame (otherwise we just flow into it)

		else if (self.frame == playerframe::deathd1 && self.frame <= playerframe::deathd8 && self.legsent.animnum != BOTH_DEATH2)
		{
			sexedsound(self, "death2.wav");
			ForceToAnim(BOTH_DEATH2);
		}
		else if (self.frame == playerframe::deathd9 && self.frame2 != playerframe::deathd8)
			ForceToAnim(BOTH_DEAD2);	//suddenly appeared with this frame (otherwise we just flow into it)

		else if (self.frame == playerframe::deathe1 && self.frame <= playerframe::deathe8 && self.legsent.animnum != BOTH_DEATH3)
		{
			sexedsound(self, "death3.wav");
			ForceToAnim(BOTH_DEATH3);
		}
		else if (self.frame == playerframe::deathe9 && self.frame2 != playerframe::deathe8)
			ForceToAnim(BOTH_DEAD3);	//suddenly appeared with this frame (otherwise we just flow into it)

		else if ((self.frame  == playerframe::nailatt1 || self.frame  == playerframe::nailatt2) &&
			 (self.frame2 != playerframe::nailatt1 && self.frame2 != playerframe::nailatt2))
			ForceToAnim(TORSO_ATTACK);
		else if ((self.frame  == playerframe::light1 || self.frame  == playerframe::light2) &&
			 (self.frame2 != playerframe::light1 && self.frame2 != playerframe::light2))
			ForceToAnim(TORSO_ATTACK);
		else if (self.frame == playerframe::rockatt1)
			ForceToAnim(TORSO_ATTACK);
		else if (self.frame == playerframe::shotatt1)
			ForceToAnim(TORSO_ATTACK);
		else if (self.frame == playerframe::axatt1)
			ForceToAnim(TORSO_ATTACK2);
		else if (self.frame == playerframe::axattb1)
			ForceToAnim(TORSO_ATTACK2);
		else if (self.frame == playerframe::axattc1)
			ForceToAnim(TORSO_ATTACK2);
		else if (self.frame == playerframe::axattd1)
			ForceToAnim(TORSO_ATTACK2);
		else if (self.frame2 >= playerframe::axdeth1 && self.frame2 <= playerframe::deathe9 && !(self.frame >= playerframe::axdeth1 && self.frame <= playerframe::deathe9))
		{	//respawned
			ForceToAnim(LEGS_IDLE);
			ForceToAnim(TORSO_STAND);
		}
		self.frame2 = self.frame;
	}

	//add a dynamic light around the player if they have any powerups.
	if (self.sveffects & SVE_QUAD)
		ang_z = 1;
	if (self.sveffects & SVE_GOD)
		ang_x = 1;
	ang_y = 0;
	if (ang != '0 0 0')
	{
		dynamiclight_add(self.origin, 400, ang);
	}

	//if they're meant to be transparent, propogate that.
	self.legsent.alpha = self.torsoent.alpha = self.headent.alpha = self.weaponent.alpha = self.alpha;
	//LegsUpdateAnim needs its origin early, for stff
	self.legsent.origin = self.origin;

	//update the animation lerps
	makevectors(self.legsent.angles);
	LegsUpdateAnim();
	TorsoUpdateAnim();

	//add the legs into the scene, with their lagged angles.
	AddModelWithEffects(self.legsent);
	//legs are in place, matricies are set up for them.

	//work out where to put the torso by querying the tag
	self.torsoent.origin = rotatevectorsbytag(self.legsent, self.torsoent.tag_index);//figure out the torso position
	//torso's angles are not lagged, and must always point directly at the target, so rotate by the extra angles.
	ang = self.angles;
	ang_y = self.angles_y - self.legsent.angles_y;	//keep the angle on the legs
	ang_x*=2;
	if (self.legsent.animnum > BOTH_DEAD3)
		rotatevectorsbyangle(ang);	//rotate the torso (when dead the whole thing acts as one model with no custom angles inside)
	AddModelWithEffects(self.torsoent);
	//torso is now added to the scene.

	//save the direction we're looking in for the weapon which is added after the head.
	tf = v_forward;
	tr = v_right;
	tu = v_up;

	//now work out where to put the head
	self.headent.origin = rotatevectorsbytag(self.torsoent, self.headent.tag_index);//
	ang_y = sin(time)*22.5;
	ang_x = cos(time*0.4532)*11.25;
	if (self.legsent.animnum > BOTH_DEAD3)
		rotatevectorsbyangle(ang);	//make the head around a bit
	AddModelWithEffects(self.headent);
	//head is now in place

	if (self.frame >= playerframe::axdeth1 && self.frame <= playerframe::deathe9)
		return;	//don't show the weapon in death frames.

	//and revert the matrix back to how it was before the head.
	v_forward = tf;
	v_right = tr;
	v_up = tu;

	move = self.sveffects&15;
	switch (move)
	{
	case 12:	//axe
		weaponname = "models/weapons2/gauntlet/gauntlet";
		break;
	default:
	case 0:	//shotgun
		weaponname = "models/weapons2/railgun/railgun";	//well... the sniper's prefered weapon, at least. :p
		break;
	case 1:	//super shotgun
		weaponname = "models/weapons2/shotgun/shotgun";
		break;
	case 2:	//nailgun
		weaponname = "models/weapons2/machinegun/machinegun";
		break;
	case 3:	//super nailgun
		weaponname = "models/weapons2/plasma/plasma";
		break;
	case 4: //grenade launcher
		weaponname = "models/weapons2/grenadel/grenadel";
		break;
	case 5: //rocket launcher
		weaponname = "models/weapons2/rocketl/rocketl";
		break;
	case 6: //lightning gun
		weaponname = "models/weapons2/lightning/lightning";
		break;
	}

	setmodel(self.weaponent, strcat(weaponname, ".md3"));

	//rotate by a tag on the torso
	self.weaponent.origin = rotatevectorsbytag(self.torsoent, self.weaponent.tag_index);//place the weapon in the hand
	//and add it.
	AddModelWithEffects(self.weaponent);

	//this check is a little wrong.
	if (self.torsoent.animnum == TORSO_ATTACK || self.torsoent.animnum == TORSO_ATTACK2)
	{
		move = gettagindex(self.weaponent, "tag_flash");
		if (move)
		{
			//they're shooting something. make a muzzleflash appear at the end of their weapon.
			self.weaponent.origin = rotatevectorsbytag(self.weaponent, move);
			setmodel(self.weaponent, strcat(weaponname, "_flash.md3"));
			AddModelWithEffects(self.weaponent);
		}
	}
};

//remove our attached models, restoring the player model to being a boring player.
nonstatic void() Anim_UnsetModel =
{
	if (self.torsoent)
		remove(self.torsoent);
	if (self.headent)
		remove(self.headent);
	if (self.legsent)
		remove(self.legsent);
	if (self.weaponent)
		remove(self.weaponent);

	self.torsoent = world;
	self.headent = world;
	self.legsent = world;
	self.weaponent = world;
	self.modelnum = -1;

	setmodel(self, self.model);
};

nonstatic float() Anim_GetGender =
{
	if (self.headent)
		return anim_gender[self.headent.modelnum];
	return GENDER_DEFAULT;
};

//Attempts to read the animation file for the named q3 player model
//-1 on failure.
nonstatic float(string modname) Anim_ReadAnimationFile =
{
	local float modnum;
	local string str;
	local float file;
	local float sequencenum = 0;
	local float stupid;

	if (modname == "")
		return -1;

	for (modnum = 0; modnum < MAXMODELS; modnum++)
	{
		if (anim_name[modnum] == modname)
			return modnum;	//already loaded
		if (anim_name[modnum] == "")
			break;	//empty slot
	}
	if (modnum == MAXMODELS)
	{	//list is full
		print("Too many models\n");
		return -1;
	}
	else
	{
		str = strcat("models/players/", modname, "/animation.cfg");

		file = fopen(str, FILE_READ);
		if (file < 0)
		{
			print(sprintf("fopen %S failed\n", str));
			return -1;
		}

		modname= strzone(modname);
		anim_name[modnum] = modname;

		//precache them.
		precache_model(strcat("models/players/", modname, "/upper.md3"));
		precache_model(strcat("models/players/", modname, "/lower.md3"));
		precache_model(strcat("models/players/", modname, "/head.md3"));

		anim_headmodel[modnum] = getmodelindex(strcat("models/players/", modname, "/head.md3"));

		//default general values
		anim_gender[modnum] = GENDER_DEFAULT;
		anim_headoffset[modnum] = '0 0 0';

		for (;;)
		{
			str = fgets(file);
			if not (str) break;

			tokenize(str);
			str = argv(0);
			if (str == "")
				continue;
			else if (str == "sex")
			{
				str = argv(1);
				if (str == "m" || str == "M")
					anim_gender[modnum] = GENDER_MALE;
				else if (str == "f" || str == "F")
					anim_gender[modnum] = GENDER_FEMALE;
				else	//n or N
					anim_gender[modnum] = GENDER_NEUTER;
			}
			else if (str == "headoffset")
			{
				vector v;
				v_x = stof(argv(1));
				v_y = stof(argv(2));
				v_z = stof(argv(3));
				anim_headoffset[modnum] = v;
			}
			else if (str == "footsteps")
			{
				//we don't play footsteps anyway
			}
			else if (str2chr(str,0) >= '0' && str2chr(str,0) <= '9')
			{
				if (sequencenum == NUMANIMS)
					print("animation file \"" "models/players/", modname, "/animation.cfg" "\" contains too many animations\n");
				else
				{	
					if (sequencenum == LEGS_WALKCR)	//for some stupid reason, the leg frames start where the torso leaves
					{
						stupid = stof(str);
						stupid = stupid - anim_firstframe[(modnum * NUMANIMS) + TORSO_GESTURE];
					}
					else stupid = 0;
					anim_firstframe[(modnum * NUMANIMS) + sequencenum] = stof(str) - stupid;
					anim_numframes[(modnum * NUMANIMS) + sequencenum] = stof(argv(1));
					anim_loopingframes[(modnum * NUMANIMS) + sequencenum] = stof(argv(2));
					anum_framespersecond[(modnum * NUMANIMS) + sequencenum] = stof(argv(3));
					sequencenum++;
				}
			}
			else
				print("animation.cfg contains unrecognised token ", str, "\n");
		}

		fclose(file);

		if (sequencenum != NUMANIMS)
			print("animation.cfg is incompleate\n");
	}
	return modnum;
}

//attempts to apply a player model/skin to the given entity.
//this may load the configuration.cfg
//skinname is of the form: ranger/default
nonstatic float(string skinname) Anim_SetModel =
{
	local string lowermodelname;
	local string uppermodelname;
	local string headmodelname;

	local string lowerskinname;
	local string upperskinname;
	local string headskinname;

	local float lowermodnum;
	local float uppermodnum;
	local float headmodnum;

	local float slashpos;

	tokenize(skinname);
	lowermodelname = argv(2);
	uppermodelname = argv(1);
	headmodelname = argv(0);

	slashpos = strstrofs(lowermodelname, "/");
	lowerskinname = substring(lowermodelname, slashpos+1, -1);
	lowermodelname = substring(lowermodelname, 0, slashpos);

	slashpos = strstrofs(uppermodelname, "/");
	upperskinname = substring(uppermodelname, slashpos+1, -1);
	uppermodelname = substring(uppermodelname, 0, slashpos);

	slashpos = strstrofs(headmodelname, "/");
	headskinname = substring(headmodelname, slashpos+1, -1);
	headmodelname = substring(headmodelname, 0, slashpos);

	//seeing as we support loading each part from a different player model (well, q3 does)
	//we load it three times.
	lowermodnum = Anim_ReadAnimationFile(lowermodelname);
	uppermodnum = Anim_ReadAnimationFile(uppermodelname);
	headmodnum = Anim_ReadAnimationFile(headmodelname);

	//all failed
	if (lowermodnum < 0 && uppermodnum < 0 && headmodnum < 0)
		return false;	//failed to load the animation.

	//make sure that all three parts are valid or something.
	if (headmodnum < 0)
	{
		if (lowermodnum < 0)
		{
			headmodnum = uppermodnum;
			headmodelname = uppermodelname;
			headskinname = upperskinname;
		}
		else
		{
			headmodnum = lowermodnum;
			headmodelname = lowermodelname;
			headskinname = lowerskinname;
		}
	}
	if (lowermodnum < 0)
	{
		lowermodnum = headmodnum;
		lowermodelname = headmodelname;
		lowerskinname = headskinname;
	}
	if (uppermodnum < 0)
	{
		uppermodnum = headmodnum;
		uppermodelname = headmodelname;
		upperskinname = headskinname;
	}

	//spawn the attaching ents
	if (!self.torsoent)
		self.torsoent = spawn();
	if (!self.headent)
		self.headent = spawn();
	if (!self.legsent)
		self.legsent = spawn();
	if (!self.weaponent)
		self.weaponent = spawn();

	//give them the correct model
	setmodel(self.legsent, strcat("models/players/", anim_name[lowermodnum], "/lower.md3"));
	setmodel(self.torsoent, strcat("models/players/", anim_name[uppermodnum], "/upper.md3"));
	setmodel(self.headent, strcat("models/players/", anim_name[headmodnum], "/head.md3"));

//	precache_model(AXEMODELNAME);
//	precache_model(WEAPONMODELNAME);


	self.legsent.owner = self;
	self.headent.owner = self;
	self.torsoent.owner = self;
	self.weaponent.owner = self;

	self.drawmask = MASK_NORMAL;	//general view.

	//find the tags so we don't do it every single frame
	self.torsoent.tag_index = gettagindex(self.legsent, "tag_torso");
	self.headent.tag_index = gettagindex(self.torsoent, "tag_head");
	self.weaponent.tag_index = gettagindex(self.torsoent, "tag_weapon");

	self.modelnum = headmodnum;
	self.legsent.modelnum = lowermodnum;
	self.headent.modelnum = headmodnum;
	self.torsoent.modelnum = uppermodnum;
	self.weaponent.modelnum = lowermodnum;

	self.legsent.colormap = self.colormap;
	self.headent.colormap = self.colormap;
	self.torsoent.colormap = self.colormap;
	self.weaponent.colormap = self.colormap;

	if (!lowerskinname)
		lowerskinname = "default";
	if (!upperskinname)
		upperskinname = "default";
	if (!headskinname)
		headskinname = "default";

	//find which skin number to use for the given skin file
	if (stof(lowerskinname))
		self.legsent.skin = stof(lowerskinname);
	else
		self.legsent.skin = skinforname(self.legsent.modelindex, strcat("models/players/", anim_name[lowermodnum], "/lower_", lowerskinname, ".skin"));
	if (stof(upperskinname))
		self.torsoent.skin = stof(upperskinname);
	else
		self.torsoent.skin = skinforname(self.torsoent.modelindex, strcat("models/players/", anim_name[uppermodnum], "/upper_", upperskinname, ".skin"));
	if (stof(headskinname))
		self.headent.skin = stof(headskinname);
	else
		self.headent.skin = skinforname(self.headent.modelindex, strcat("models/players/", anim_name[headmodnum], "/head_", headskinname, ".skin"));

	//setup the initial sequences.
	ForceToAnim(LEGS_IDLE);
	ForceToAnim(TORSO_STAND);

	//so it takes effect fully and immediatly... well, the first time its drawn, anyway.
	self.legsent.framechangetime = -50;
	self.torsoent.framechangetime = -50;
	self.legsent.frame2 = -1;
	self.torsoent.frame2 = -1;

	self.frame2 = -1;
	self.lerpfrac = 0;

	return true;
};

entity(entity src) CloneModel =
{
	local entity dest;
	dest = spawn();

	dest.modelnum = src.modelnum;
	dest.animnum = src.animnum;
	dest.colormap = src.colormap;
	dest.tag_index = src.tag_index;
	dest.skin = src.skin;
	dest.predraw = src.predraw;
	dest.frame = src.frame;
	dest.frame2 = src.frame2;
	dest.model = src.model;
	dest.modelindex = src.modelindex;
	dest.drawmask = src.drawmask;
	dest.origin = src.origin;
	dest.angles = src.angles;
	dest.mins = src.mins;
	dest.maxs = src.maxs;
	
	return dest;
};

nonstatic entity() Anim_DupModel =
{
	local entity o, n;
	o = self;
	n = self = CloneModel(o);

	self.legsent = CloneModel(o.legsent);
	self.headent = CloneModel(o.headent);
	self.torsoent = CloneModel(o.torsoent);
	self.weaponent = CloneModel(o.weaponent);
	self.velocity = o.velocity;


	self = o;
	return n;
};

nonstatic float(string skinname) Anim_GetHeadModelIndex =
{
	float slashpos;
	string modelname;
	float modnum;

	tokenize(skinname);
	modelname = argv(0);
	if (modelname == "")
		return 0;	//an invalid modelindex.

	slashpos = strstrofs(modelname, "/");
	modelname = substring(modelname, 0, slashpos);

	//seeing as we support loading each part from a different player model (well, q3 does)
	//we load it three times.
	modnum = Anim_ReadAnimationFile(modelname);
	if (modnum < 0)
		return 0;

	return anim_headmodel[modnum];
};

nonstatic float(string skinname) Anim_GetHeadSkinNumber =
{
	float slashpos;
	string modelname;
	float modnum;

	tokenize(skinname);
	modelname = argv(0);
	if (modelname == "")
		return 0;	//0 = default

	slashpos = strstrofs(modelname, "/");
	skinname = substring(modelname, slashpos+1, -1);
	modelname = substring(modelname, 0, slashpos);

	if (stof(skinname))
		return stof(skinname);

	//seeing as we support loading each part from a different player model (well, q3 does)
	//we load it three times.
	modnum = Anim_ReadAnimationFile(modelname);
	if (modnum < 0)
		return 0;

	if (!skinname)
		skinname = "default";

	return skinforname(anim_headmodel[modnum], strcat("models/players/", modelname, "/head_", skinname, ".skin"));
};

nonstatic vector(string skinname) Anim_GetHeadOffset =
{
	float slashpos;
	string modelname;
	float modnum;

	tokenize(skinname);
	modelname = argv(0);
	if (modelname == "")
		return '0 0 0';	//an invalid modelindex.

	slashpos = strstrofs(modelname, "/");
	modelname = substring(modelname, 0, slashpos);

	//seeing as we support loading each part from a different player model (well, q3 does)
	//we load it three times.
	modnum = Anim_ReadAnimationFile(modelname);
	if (modnum < 0)
		return '0 0 0';
	return anim_headoffset[modnum];
}



static float(float channel, string soundname, vector pos, float vol, float attenuation, float flags) ServerSoundStartRequest =
{	//the server started a sound on an entity that the csqc has control over.
	if (!self.headent)
	{
		return false;
	}

	if (soundname == "player/plyrjmp8.wav")
	{
		if (self == player_local)
			if (CVARF(cg_noselfjumpsound))
				return true;	//option to disable jump noise for local player.
		sexedsound(self, "jump1.wav");
		return true;
	}
	if (soundname == "player/gasp1.wav")
	{
		sexedsound(self, "gasp.wav");
		return true;
	}
	if (soundname == "player/gasp2.wav")
	{
		sexedsound(self, "gasp.wav");
		return true;
	}
	if (soundname == "player/land.wav")
	{
		sexedsound(self, "fall1.wav");
		return true;
	}
	if (soundname == "player/land2.wav")
	{
		sexedsound(self, "fall1.wav");
		return true;
	}

	if (soundname == "player/pain1.wav")
	{
		sexedsound(self, "pain25_1.wav");
		return true;
	}
	if (soundname == "player/pain2.wav")
	{
		sexedsound(self, "pain50_1.wav");
		return true;
	}
	if (soundname == "player/pain3.wav")
	{
		sexedsound(self, "pain75_1.wav");
		return true;
	}
	if (soundname == "player/pain4.wav")
	{
		sexedsound(self, "pain100_1.wav");
		return true;
	}
	if (soundname == "player/pain5.wav")
	{
		sexedsound(self, "pain75_1.wav");
		return true;
	}
	if (soundname == "player/pain6.wav")
	{
		sexedsound(self, "pain100_1.wav");
		return true;
	}

	if (soundname == "player/h2odeath.wav")
	{
		sexedsound(self, "drown.wav");
		return true;
	}
	if (soundname == "player/death1.wav")	//normal deaths come from the player animations.
		return true;
	if (soundname == "player/death2.wav")
		return true;
	if (soundname == "player/death3.wav")
		return true;
	if (soundname == "player/death4.wav")
		return true;
	if (soundname == "player/death5.wav")
		return true;

	return false;
};

//version that breaks when the ent is not necessarily known to us, called only when 'self' is a known CSQC entity.
float(float channel, string soundname, vector org, float vol, float attenuation, float flags) CSQC_ServerSound =
{
	return ServerSoundStartRequest(channel, soundname, org, vol, attenuation, flags);
}
//version that lets the qc handle any ent fixups.
float(float sventnum, float channel, string soundname, float vol, float att, vector org, float pitchmod, float flags) CSQC_Event_Sound =
{
	self = findfloat(world, entnum, sventnum);
	if (self)
		return ServerSoundStartRequest(channel, soundname, org, vol, att, flags);
	return false;
};
#endif
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.