1351 lines
37 KiB
Plaintext
1351 lines
37 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
|
||
|
|
||
|
*/
|
||
|
|
||
|
|
||
|
|
||
|
//These $frame lines of course state the quake player frames that we're using as cues for which animation to play.
|
||
|
//(less work that way)
|
||
|
|
||
|
//
|
||
|
// running
|
||
|
//
|
||
|
$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6
|
||
|
|
||
|
$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6
|
||
|
|
||
|
//
|
||
|
// standing
|
||
|
//
|
||
|
$frame stand1 stand2 stand3 stand4 stand5
|
||
|
|
||
|
$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6
|
||
|
$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12
|
||
|
|
||
|
|
||
|
//
|
||
|
// pain
|
||
|
//
|
||
|
$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6
|
||
|
|
||
|
$frame pain1 pain2 pain3 pain4 pain5 pain6
|
||
|
|
||
|
|
||
|
//
|
||
|
// death
|
||
|
//
|
||
|
|
||
|
$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6
|
||
|
$frame axdeth7 axdeth8 axdeth9
|
||
|
|
||
|
$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8
|
||
|
$frame deatha9 deatha10 deatha11
|
||
|
|
||
|
$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8
|
||
|
$frame deathb9
|
||
|
|
||
|
$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8
|
||
|
$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 deathc15
|
||
|
|
||
|
$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7
|
||
|
$frame deathd8 deathd9
|
||
|
|
||
|
$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7
|
||
|
$frame deathe8 deathe9
|
||
|
|
||
|
//
|
||
|
// attacks
|
||
|
//
|
||
|
$frame nailatt1 nailatt2
|
||
|
|
||
|
$frame light1 light2
|
||
|
|
||
|
$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6
|
||
|
|
||
|
$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6
|
||
|
|
||
|
$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6
|
||
|
|
||
|
$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6
|
||
|
|
||
|
$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6
|
||
|
|
||
|
$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6
|
||
|
|
||
|
//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];
|
||
|
|
||
|
//the genders you can have.
|
||
|
//we're generous with three.
|
||
|
enum {
|
||
|
GENDER_NEUTER,
|
||
|
GENDER_MALE,
|
||
|
GENDER_FEMALE
|
||
|
};
|
||
|
|
||
|
//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 (cvar("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);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
str = strcat("player/sarge/", soundname); // :(
|
||
|
precache_sound(str);
|
||
|
sound(self, CHAN_VOICE, str, 1, 1);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
//our player's frames changed to some new sequence.
|
||
|
//our animation function will animate acordingly.
|
||
|
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 firstframe;
|
||
|
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.
|
||
|
if (fnum >= numframes)
|
||
|
{
|
||
|
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;
|
||
|
|
||
|
ret = true;
|
||
|
}
|
||
|
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;
|
||
|
float onground;
|
||
|
tracebox(self.origin, self.mins, self.maxs, self.origin-'0 0 2', FALSE, self);
|
||
|
|
||
|
if (trace_fraction == 1)
|
||
|
inair = self.velocity_z;
|
||
|
|
||
|
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))
|
||
|
{
|
||
|
if (self.frame >= $axdeth1 && self.frame <= $deathe9)
|
||
|
return; //dead.
|
||
|
|
||
|
if (self.frame == $nailatt1 || self.frame == $nailatt2 ||
|
||
|
self.frame == $light1 || self.frame == $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 ((self.frame >= $axrun1 && self.frame <= $axrun6) ||
|
||
|
(self.frame >= $axstnd1 && self.frame <= $axstnd12) ||
|
||
|
(self.frame >= $axpain1 && self.frame <= $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;
|
||
|
};
|
||
|
|
||
|
.float ideal_yaw;
|
||
|
.float yaw_speed;
|
||
|
.float tag_index;
|
||
|
|
||
|
//this function is called inside your base entity's predraw function (which should then return false)
|
||
|
void() Anim_Draw =
|
||
|
{
|
||
|
vector tf, tr, tu;
|
||
|
vector ang;
|
||
|
float move;
|
||
|
string weaponname;
|
||
|
|
||
|
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 >= $axdeth1 && self.frame <= $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 >= $axdeth1 && self.frame <= $axdeth8 && self.legsent.animnum != BOTH_DEATH1)
|
||
|
{
|
||
|
sexedsound(self, "death1.wav");
|
||
|
ForceToAnim(BOTH_DEATH1);
|
||
|
}
|
||
|
else if (self.frame == $axdeth9 && self.frame2 != $axdeth8)
|
||
|
ForceToAnim(BOTH_DEAD1); //suddenly appeared with this frame (otherwise we just flow into it)
|
||
|
|
||
|
else if (self.frame == $deatha1 && self.frame <= $deatha10 && self.legsent.animnum != BOTH_DEATH2)
|
||
|
{
|
||
|
sexedsound(self, "death2.wav");
|
||
|
ForceToAnim(BOTH_DEATH2);
|
||
|
}
|
||
|
else if (self.frame == $deatha11 && self.frame2 != $deatha10)
|
||
|
ForceToAnim(BOTH_DEAD2); //suddenly appeared with this frame (otherwise we just flow into it)
|
||
|
|
||
|
else if (self.frame == $deathb1 && self.frame <= $deathb8 && self.legsent.animnum != BOTH_DEATH3)
|
||
|
{
|
||
|
sexedsound(self, "death3.wav");
|
||
|
ForceToAnim(BOTH_DEATH3);
|
||
|
}
|
||
|
else if (self.frame == $deathb9 && self.frame2 != $deathb8)
|
||
|
ForceToAnim(BOTH_DEAD3); //suddenly appeared with this frame (otherwise we just flow into it)
|
||
|
|
||
|
else if (self.frame == $deathc1 && self.frame <= $deathc14 && self.legsent.animnum != BOTH_DEATH1)
|
||
|
{
|
||
|
sexedsound(self, "death1.wav");
|
||
|
ForceToAnim(BOTH_DEATH1);
|
||
|
}
|
||
|
else if (self.frame == $deathc15 && self.frame2 != $deathc14)
|
||
|
ForceToAnim(BOTH_DEAD1); //suddenly appeared with this frame (otherwise we just flow into it)
|
||
|
|
||
|
else if (self.frame == $deathd1 && self.frame <= $deathd8 && self.legsent.animnum != BOTH_DEATH2)
|
||
|
{
|
||
|
sexedsound(self, "death2.wav");
|
||
|
ForceToAnim(BOTH_DEATH2);
|
||
|
}
|
||
|
else if (self.frame == $deathd9 && self.frame2 != $deathd8)
|
||
|
ForceToAnim(BOTH_DEAD2); //suddenly appeared with this frame (otherwise we just flow into it)
|
||
|
|
||
|
else if (self.frame == $deathe1 && self.frame <= $deathe8 && self.legsent.animnum != BOTH_DEATH3)
|
||
|
{
|
||
|
sexedsound(self, "death3.wav");
|
||
|
ForceToAnim(BOTH_DEATH3);
|
||
|
}
|
||
|
else if (self.frame == $deathe9 && self.frame2 != $deathe8)
|
||
|
ForceToAnim(BOTH_DEAD3); //suddenly appeared with this frame (otherwise we just flow into it)
|
||
|
|
||
|
else if ((self.frame == $nailatt1 || self.frame == $nailatt2) &&
|
||
|
(self.frame2 != $nailatt1 && self.frame2 != $nailatt2))
|
||
|
ForceToAnim(TORSO_ATTACK);
|
||
|
else if ((self.frame == $light1 || self.frame == $light2) &&
|
||
|
(self.frame2 != $light1 && self.frame2 != $light2))
|
||
|
ForceToAnim(TORSO_ATTACK);
|
||
|
else if (self.frame == $rockatt1)
|
||
|
ForceToAnim(TORSO_ATTACK);
|
||
|
else if (self.frame == $shotatt1)
|
||
|
ForceToAnim(TORSO_ATTACK);
|
||
|
else if (self.frame == $axatt1)
|
||
|
ForceToAnim(TORSO_ATTACK2);
|
||
|
else if (self.frame == $axattb1)
|
||
|
ForceToAnim(TORSO_ATTACK2);
|
||
|
else if (self.frame == $axattc1)
|
||
|
ForceToAnim(TORSO_ATTACK2);
|
||
|
else if (self.frame == $axattd1)
|
||
|
ForceToAnim(TORSO_ATTACK2);
|
||
|
else if (self.frame2 >= $axdeth1 && self.frame2 <= $deathe9 && !(self.frame >= $axdeth1 && self.frame <= $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')
|
||
|
{
|
||
|
adddynamiclight(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 >= $axdeth1 && self.frame <= $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.
|
||
|
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);
|
||
|
};
|
||
|
|
||
|
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.
|
||
|
float(string modname) Anim_ReadAnimationFile =
|
||
|
{
|
||
|
local float modnum;
|
||
|
local string str;
|
||
|
local float file;
|
||
|
local float sequencenum;
|
||
|
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("fopen ", modname, " failed\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
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] = modelindex_for_name(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];
|
||
|
}
|
||
|
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
|
||
|
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 string str;
|
||
|
local float slashpos;
|
||
|
local float file;
|
||
|
local float sequencenum;
|
||
|
local float stupid;
|
||
|
|
||
|
tokenize(skinname);
|
||
|
lowermodelname = argv(2);
|
||
|
uppermodelname = argv(1);
|
||
|
headmodelname = argv(0);
|
||
|
|
||
|
slashpos = strstrofs(lowermodelname, "/");
|
||
|
#ifdef WORKINDP
|
||
|
lowerskinname = substring(lowermodelname, slashpos+1, strlen(lowermodelname) - (slashpos+1));
|
||
|
#else
|
||
|
lowerskinname = substring(lowermodelname, slashpos+1, -1);
|
||
|
#endif
|
||
|
lowermodelname = substring(lowermodelname, 0, slashpos);
|
||
|
|
||
|
slashpos = strstrofs(uppermodelname, "/");
|
||
|
#ifdef WORKINDP
|
||
|
upperskinname = substring(uppermodelname, slashpos+1, strlen(uppermodelname) - (slashpos+1));
|
||
|
#else
|
||
|
upperskinname = substring(uppermodelname, slashpos+1, -1);
|
||
|
#endif
|
||
|
uppermodelname = substring(uppermodelname, 0, slashpos);
|
||
|
|
||
|
slashpos = strstrofs(headmodelname, "/");
|
||
|
#ifdef WORKINDP
|
||
|
headskinname = substring(headmodelname, slashpos+1, strlen(headmodelname) - (slashpos+1));
|
||
|
#else
|
||
|
headskinname = substring(headmodelname, slashpos+1, -1);
|
||
|
#endif
|
||
|
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;
|
||
|
};
|
||
|
|
||
|
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;
|
||
|
};
|
||
|
|
||
|
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];
|
||
|
};
|
||
|
|
||
|
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, "/");
|
||
|
#ifdef WORKINDP
|
||
|
skinname = substring(modelname, slashpos+1, strlen(modelname) - (slashpos+1));
|
||
|
#else
|
||
|
skinname = substring(modelname, slashpos+1, -1);
|
||
|
#endif
|
||
|
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"));
|
||
|
};
|
||
|
|
||
|
vector(string skinname) Anim_GetHeadOffset =
|
||
|
{
|
||
|
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_headoffset[modnum];
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
float(float channel, string soundname, vector pos, float vol, float attenuation) CSQC_ServerSound =
|
||
|
{ //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 (cvar("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;
|
||
|
};
|
||
|
|
||
|
float(float sventnum, float channel, string soundname, float vol, float att, vector org) CSQC_Event_Sound =
|
||
|
{
|
||
|
self = findfloat(world, entnum, sventnum);
|
||
|
if (self)
|
||
|
return CSQC_ServerSound(channel, soundname, org, vol, att);
|
||
|
return false;
|
||
|
};
|