fteqw/quakec/csqctest/src/cs/q3playerm.qc

1261 lines
36 KiB
Plaintext

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