Single Player Entities

The Dark Conjunction also has some entities that can be very useful in a single player game. In this section we will look at adding them to our source code.

target_earthquake

This entity creates an earthquake effect, causing the screen to shake. An NPC uses this effect as an attack so most of the code already exists. Add the following to the entity definition file for your level editor :

/*QUAKED target_earthquake (1 0 0) (-16 -16 -24) (16 16 32)
starts earthquake
"length" - length in seconds (2-32, in steps of 2)
"intensity" - strength of earthquake (1-16)
*/

The usage, from the Dark Conjunction entity manual is as follows :

Design tips: You can control the length and the intensity of the earthquake.

Keys
length:
Values are between 2 and 32 and are in seconds. Only powers of two can be used to determine length.

intensity:
Values are between 1 and 16. The higher the value the more intense the earthquake is.

targetname:
To trigger the earthquake.

The source code changes are :

In g_savestate.c about line 99 add :

extern void func_timer_use ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
extern void target_use_end_level ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
#ifdef SINGLEPLAYER // npc
extern void trig_FinishSpawningNPC ( gentity_t * ent , gentity_t * a , gentity_t * b ) ;
#endif
#ifdef SINGLEPLAYER // entity
extern void target_earthquake ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
#endif

In g_savestate.c about line 191 add :

	{"func_timer_use", (byte *)func_timer_use},
 {"target_use_end_level", (byte *)target_use_end_level},
#ifdef SINGLEPLAYER // npc
{"trig_FinishSpawningNPC", (byte *)trig_FinishSpawningNPC},
#endif
#ifdef SINGLEPLAYER // entity {"target_earthquake", (byte *)target_earthquake},
#endif

In g_spawn.c about line 171 add :

#ifdef SINGLEPLAYER // entity
void SP_target_earthquake (gentity_t *ent);
#endif

void SP_light (gentity_t *self);
void SP_info_null (gentity_t *self);

In g_spawn.c about line 258 add :

#ifdef SINGLEPLAYER // entity
{"target_earthquake", SP_target_earthquake},
#endif

{"light", SP_light},
{"path_corner", SP_path_corner},

In g_target.c at the end of the file add :

#ifdef SINGLEPLAYER // entity
/*QUAKED target_earthquake (1 0 0) (-16 -16 -24) (16 16 32)
starts earthquake
"length" - length in seconds (2-32, in steps of 2)
"intensity" - strength of earthquake (1-16)
*/
void target_earthquake( gentity_t *self, gentity_t *other, gentity_t *activator ) {
G_AddEvent(self, EV_EARTHQUAKE, self->s.generic1);
}
void SP_target_earthquake( gentity_t *self ) {
int param;
float length; // length in seconds (2 to 32)
float intensity; // intensity (1 to 16)
int length_;
int intensity_;
// read parameters
G_SpawnFloat( "length", "1000", &length);
G_SpawnFloat( "intensity", "50", &intensity);
if (length<2) length=2;
if (length>32) length=32;
if (intensity<1) intensity=1;
if (intensity>16) intensity=16;
// adjust parameters
length_ = ((int)(length) - 2)/2;
intensity_ = (int)intensity-1;
param = ( intensity_ | (length_<<4) );
self->s.generic1=param;
self->use = target_earthquake;
self->s.eType = ET_EVENTS;
trap_LinkEntity (self);
}
#endif

target_player_stop

This entity freezes player movement for a specific amount of time, although the player can still look around. Add the following to the entity definition file for your level editor :

/*QUAKED target_player_stop (1 0 0) (-16 -16 -24) (16 16 32) black_bars
stops player for "wait" seconds
if "blackbars" is set, cinema black bars appear during the player is stopped
*/

The usage, from the Dark Conjunction entity manual is as follows :

Keys
wait:
The amount of time the player is stopped. Each unit is equivalent to two seconds. Wait 2 will freeze player for four seconds.

targetname:
To trigger the player stop.

Check Boxes/Spawnflags
black_bars:
This makes a couple of black bars display on the top and the bottom of the screen as long as the event lasts. Kind of a wide screen effect.

Known bug: It seems that if the target_player_stop shares the same trigger with many other events when it becomes active and the player tries to move the screen kind of starts jumping like if the movement succeeded only for a very small fraction of time. If this happens create another trigger that only shoots the target_player_stop.

The source code changes are made to both the game and cgame modules and are as follows :

In bg_public.h about line 459 add :

#ifdef SINGLEPLAYER // entity
EV_PLAYERSTOP,
#endif

EV_DEBUG_LINE,
EV_STOPLOOPINGSOUND,
EV_TAUNT,

In g_local.c in struct gentity_s about line 275 add :

	gitem_t		*item;			// for bonus items

#ifdef SINGLEPLAYER // npc
gnpc_t *npc;
npcData_t ns;
int slow_event;
#endif
#ifdef SINGLEPLAYER // entity
int stop_event;
#endif

};

In g_active.c in function ClientThink_real about line 769 add :

	// mark the time, so the connection sprite can be removed
ucmd = &ent->client->pers.cmd;
#ifdef SINGLEPLAYER // entity
if (ent->stop_event)
if (ent->stop_event>level.time)
{
ucmd->forwardmove=0;
ucmd->rightmove=0;
ucmd->upmove=0;
ucmd->buttons=0;
}
else
ent->stop_event=0;
#endif

In g_savestate.c about line 99 add :

extern void func_timer_use ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
extern void target_use_end_level ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
#ifdef SINGLEPLAYER // npc
extern void trig_FinishSpawningNPC ( gentity_t * ent , gentity_t * a , gentity_t * b ) ;
#endif
#ifdef SINGLEPLAYER // entity extern void target_earthquake ( gentity_t * self , gentity_t * other , gentity_t * activator ) ; extern void target_player_stop ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
#endif

In g_savestate.c about line 191 add :

	{"func_timer_use", (byte *)func_timer_use},
 {"target_use_end_level", (byte *)target_use_end_level},
#ifdef SINGLEPLAYER // npc
{"trig_FinishSpawningNPC", (byte *)trig_FinishSpawningNPC},
#endif
#ifdef SINGLEPLAYER // entity {"target_earthquake", (byte *)target_earthquake}, {"target_player_stop", (byte *)target_player_stop},
#endif

In g_spawn.c about line 171 add :

#ifdef SINGLEPLAYER // entity
void SP_target_player_stop (gentity_t *ent);
#endif

void SP_light (gentity_t *self);
void SP_info_null (gentity_t *self);

In g_spawn.c about line 258 add :

#ifdef SINGLEPLAYER // entity
{"target_player_stop", SP_target_player_stop},
#endif

{"light", SP_light},
{"path_corner", SP_path_corner},

In g_target.c at the end of the file add :

#ifdef SINGLEPLAYER // entity
/*QUAKED target_player_stop (1 0 0) (-16 -16 -24) (16 16 32)
stops player for "wait"*2 seconds
*/
void target_player_stop( gentity_t *self, gentity_t *other, gentity_t *activator ) {
activator->stop_event=level.time+((int)(self->wait) & 0x7F)*2000;
G_AddEvent(activator, EV_PLAYERSTOP, self->wait);
}
void SP_target_player_stop( gentity_t *self ) {
G_SpawnFloat( "wait", "1", &self->wait);
if (self->spawnflags & 1)
{
if (self->wait>127)
self->wait=127;
self->wait += 128;
}
self->use = target_player_stop;
}
#endif

In cg_local.h about line 1144 add :

#ifdef SINGLEPLAYER // entity
extern player_stop;
extern black_bars;
#endif

extern cgs_t cgs;
extern cg_t cg;

In cg_local.h about line 1497 add :

//
// cg_effects.c
//
#ifdef SINGLEPLAYER // entity
void CG_BlackBars(void);
#endif

localEntity_t *CG_SmokePuff( const vec3_t p,
const vec3_t vel,
float radius,

In cg_draw.c in function CG_DrawActive about line 2745 add :

	// draw 3D view
trap_R_RenderScene( &cg.refdef );
// restore original viewpoint if running stereo
if ( separation != 0 ) {
VectorCopy( baseOrg, cg.refdef.vieworg );
}
// draw status bar and other floating elements
CG_Draw2D();
#ifdef SINGLEPLAYER // entity
if (black_bars)
CG_BlackBars();
#endif

}

In cg_effects.c about line 25 add :

#include "cg_local.h"

#ifdef SINGLEPLAYER // entity
/*
===============
CG_BlackBars
===============
*/
void CG_BlackBars(void) {
float color[4] = {0,0,0,1}; CG_FillRect(0,0,640,50, color); CG_FillRect(0,430,640,50, color); } #endif

In cg_event.c in function CG_EntityEvent about line 841 add :

#ifdef SINGLEPLAYER // entity
case EV_PLAYERSTOP:
player_stop=cg.time+(es->eventParm&0x7F)*2000;
if (es->eventParm&0x80) black_bars=1;
break;
#endif

//=================================================================
//
// other events
//
case EV_PLAYER_TELEPORT_IN:
DEBUGNAME("EV_PLAYER_TELEPORT_IN");

In cg_predict.c about line 28 add :

#include "cg_local.h"

static pmove_t cg_pmove;
#ifdef SINGLEPLAYER // entity
int player_stop=0;
int black_bars=0;
#endif

static int cg_numSolidEntities;

In cg_predict.c in function CG_PredictPlayerState about line 509 add :

	for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) {
// get the command
trap_GetUserCmd( cmdNum, &cg_pmove.cmd );
#ifdef SINGLEPLAYER // entity
if (player_stop)
{
if (player_stop>cg.time)
{
cg_pmove.cmd.forwardmove=0;
cg_pmove.cmd.rightmove=0;
cg_pmove.cmd.upmove=0;
}
else
{
player_stop=0;
black_bars=0;
}
}
#endif

if ( cg_pmove.pmove_fixed ) {
PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd );
}

misc_model_anim

This entity allows the displaying of an animated MD3 model in your level. Add the following to the entity definition file for your level editor :

/*QUAKED misc_model_anim (1 0 0) (-16 -16 -16) (16 16 16) HIDDEN_START HIDDEN_END NOTSUSPENDED
Animated MD3, select HIDDEN_START for the model to be hidden until de animation is triggered, and HIDDEN_END for disappearing after it's finished.
"model" arbitrary .md3 file to display/animate
"speed" speed scale (not less than 1.0) - used to scale ".anim" fps data (default = 1)
"wait" 1=wait for trigger activation 0=animate w/o waiting (default = 0)
*/

The usage, from the Dark Conjunction entity manual is as follows :

Design tips: The origin of the entity is in the center of the displayed box. You can easily adjust the position and orientation of an animated model by first putting the MD3 as a normal model in the map as a guide.

Keys
model:
Model that is displayed. Must contain a complete path and the .md3 file extension.

speed:
Seems not to be working. Should change the speed scale from the .anim file (see notes).

wait:
If set to 1 must be triggered to start animating. If set to 0 animates as soon as level starts.

angle:
The angle in which the model is displayed.

Check Boxes/Spawnflags
hidden_start:
The model is invisible (not drawn) until it starts to animate.

hidden_end:
The model disappears after the animation ends.

notsuspended:
The model is dropped to floor level and has mass. It can travel on plats and trains. It will block player movement. Bounding box size depends on config file (see notes).

Notes:

Animation: To control the animation from the model you must create an animation file. Just create a text document (.txt) and change the extension to .anim. This file must contain the following data:

Start frame: The number of the starting frame. First frame is 0.
Number of frames:
The number of frames that are animated from the starting frame.

Looping frames:
The number of frames that loop after each cycle of animation from the last frames.

Frames per second:
The speed with which the animation is displayed. Minimum is 5. The higher the faster.

Example of an anim file: (This is the four arm demon in the first map (Hulk demon))

// start frame, numframes, looping, frames per second
0 215 14 10

Config file: If the model uses the notsuspended check box you can alter the bounding box it uses with a config file. To create a config file simply create a text document (.txt) and change the extension to .cfg. The default bounding box is the size of a player's bounding box. To alter the size the file must contain the coordinates of the new box. 6 coordinates are required to create a box. The first three coordinates (x,y,z) correspond to lower back arista (calculated from the center which would be (0 0 0)). The second three coordinates (x,y,z) correspond to the top front arista (also calculated from the center). If we want to create a square bounding box of 10*10*10 units at floor level the coordinates would be: ( -5 -5 -24 ) ( 5 5 -14 ). The z coordinate for the lower arista MUST be -24 for the model to appear at floor level.

Example of config file: (These are the dimensions of the bounding box of a fireball hurling monster (Ank demon). This is how the coordinates should look in the .cfg file)

( -16 -24 -24 )
( 16 24 40 )

Known bugs: Suspended models (that do not have a bounding box) are "eaten" by doors or certain movers. This means that if an animated model is too close to a door when the door is activated the model disappears. To avoid this keep animated models at a safe distance from doors (specially rotating doors).

The other thing is that check boxes tend to change when selecting, duplicating or moving the entities in the map. When you select or move animated models make sure that the check boxes are not altered after. We're not sure if this is a problem with Radiant.

The source code changes are made to both the game and cgame modules and are as follows :

In bg_public.h about line 459 add :

#ifdef SINGLEPLAYER // entity
ET_MODELANIM,
#endif

EV_DEBUG_LINE,
EV_STOPLOOPINGSOUND,
EV_TAUNT,

In g_local.c about line 658 add :

//
// g_misc.c
//
void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles );
#ifdef SINGLEPLAYER // entity
void G_RunMD3Anim( gentity_t *ent ) ;
#endif

In g_main.c in function G_RunFrame about line 1827 add :

#ifdef SINGLEPLAYER // entity
if (ent->s.eType == ET_MODELANIM) {
G_RunMD3Anim(ent);
continue;
}
#endif

if ( ent->s.eType == ET_MOVER ) {
G_RunMover( ent );
continue;
}

In g_misc.c at end of file add :

#ifdef SINGLEPLAYER // entity
void FinishSpawningMD3Anim( gentity_t *ent ) {
trace_t tr;
vec3_t dest;
ent->s.modelindex = G_ModelIndex( ent->model );
ent->s.eType = ET_MODELANIM;
if ( ! (ent->spawnflags & 4 )) {
// suspended
G_SetOrigin( ent, ent->s.origin );
} else {
// drop to floor
ent->r.contents = CONTENTS_BODY;
VectorSet( ent->r.mins, -24, -24, -24 );
VectorSet( ent->r.maxs, 24, 24, 24 );
{
char modelName[MAX_QPATH];
char fname[MAX_QPATH];
int len;
char text[10000],*text_p;
fileHandle_t f;
static char err_parse[]="Error parsing ";
trap_GetConfigstring( CS_MODELS+ent->s.modelindex ,modelName,sizeof(modelName));
strcpy(fname,modelName);
text_p=fname+strlen(fname);
while (*text_p!='.' && *text_p!='\\' && text_p!=fname)
text_p--;
if (*text_p=='.')
*text_p=0;
Q_strcat(fname,sizeof(fname),".cfg");
len = trap_FS_FOpenFile( fname, &f, FS_READ );
if ( len <= 0 )
{
G_Printf("No config file found for %s , defaults will be used.\n",modelName);
goto read_times;
}
if ( len >= sizeof( text ) - 1 )
{
G_Printf( "File %s too long\n", fname );
goto read_times;
}
trap_FS_Read( text, len, f );
text[len] = 0;
trap_FS_FCloseFile( f );
text_p=text;
Parse1DMatrix (&text_p, 3, ent->r.mins);
Parse1DMatrix (&text_p, 3, ent->r.maxs);
read_times:
;
}
VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
if ( tr.startsolid ) {
G_Printf ("FinishSpawningMD3Anim: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
G_FreeEntity( ent );
return;
}
// allow to ride movers
ent->s.groundEntityNum = tr.entityNum;
G_SetOrigin( ent, tr.endpos );
}
trap_LinkEntity (ent);
}
void ActivateAnimModel(gentity_t *ent,gentity_t *other,gentity_t *activator) {
ent->s.modelindex2++;
}
/*QUAKED misc_model_anim (1 0 0) (-16 -16 -16) (16 16 16) HIDDEN_START HIDDEN_END NOTSUSPENDED
Animated MD3, select HIDDEN_START for the model to be hidden until de animation
is triggered, and HIDDEN_END for disappearing after it's finished.
"model" arbitrary .md3 file to display/animate
"speed" speed scale (not less than 1.0) - used to scale ".anim" fps data (default = 1)
"wait" 1=wait for trigger activation 0=animate w/o waiting (default = 0)
*/
void SP_misc_model_anim( gentity_t *ent ) {
G_SpawnFloat( "speed", "1.0", &ent->speed);
if (ent->speed<1) ent->speed=1;
VectorCopy( ent->s.angles, ent->s.apos.trBase );
ent->nextthink = level.time + FRAMETIME * 2;
ent->think = FinishSpawningMD3Anim;
G_SpawnFloat( "wait", "0", &ent->wait);
ent->s.modelindex2=0;
ent->s.generic1=ent->spawnflags + ((int)(ent->speed * 16) & 0xF0); // solo 3 flags
if (ent->wait==0)
ActivateAnimModel(ent,ent,ent);
else
ent->use = ActivateAnimModel;
}
void G_RunMD3Anim( gentity_t *ent ) {
vec3_t origin;
trace_t tr;
int contents;
int mask;
// if groundentity has been set to -1, it may have been pushed off an edge
if ( ent->s.groundEntityNum == -1 ) {
if ( ent->s.pos.trType != TR_GRAVITY ) {
ent->s.pos.trType = TR_GRAVITY;
ent->s.pos.trTime = level.time;
}
}
if ( ent->s.pos.trType == TR_STATIONARY ) {
// check think function
G_RunThink( ent );
return;
}
// get current position
BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
// trace a line from the previous position to the current position
if ( ent->clipmask ) {
mask = ent->clipmask;
} else {
mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
}
trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
ent->r.ownerNum, mask );
VectorCopy( tr.endpos, ent->r.currentOrigin );
if ( tr.startsolid ) {
tr.fraction = 0;
}
trap_LinkEntity( ent ); // FIXME: avoid this for stationary?
// check think function
G_RunThink( ent );
if ( tr.fraction == 1 ) {
return;
}
// if it is in a nodrop volume, remove it
contents = trap_PointContents( ent->r.currentOrigin, -1 );
if ( contents & CONTENTS_NODROP ) {
G_FreeEntity( ent );
return;
}
}
#endif

In g_mover.c in function G_MoverPush about line 358 add :

		// only push items and players
#ifndef SINGLEPLAYER // npc
if ( check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) {
#else
if ( check->s.eType != ET_ITEM && check->s.eType != ET_NPC &&
#ifdef SINGLEPLAYER // entity
check->s.eType!=ET_MODELANIM &&
#endif

check->s.eType != ET_PLAYER && !check->physicsObject ) {
#endif
continue;
}

In g_savestate.c about line 54 add :

extern void func_timer_think ( gentity_t * self ) ;
#ifdef SINGLEPLAYER // npc
extern void ank_shot_think ( gentity_t * ent ) ;
extern void FinishSpawningNPC ( gentity_t * ent ) ;
#endif
#ifdef SINGLEPLAYER // entity
extern void FinishSpawningMD3Anim ( gentity_t * ent ) ;
#endif

In g_savestate.c about line 99 add :

extern void func_timer_use ( gentity_t * self , gentity_t * other , gentity_t * activator ) ;
#ifdef SINGLEPLAYER // npc
extern void trig_FinishSpawningNPC ( gentity_t * ent , gentity_t * a , gentity_t * b ) ;
#endif
#ifdef SINGLEPLAYER // entity
extern void ActivateAnimModel ( gentity_t * ent , gentity_t * other , gentity_t * activator ) ;
#endif

In g_savestate.c about line 143 add :

	{"func_timer_think", (byte *)func_timer_think},
#ifdef SINGLEPLAYER // npc
{"ank_shot_think", (byte *)ank_shot_think},
{"FinishSpawningNPC", (byte *)FinishSpawningNPC},
#endif
#ifdef SINGLEPLAYER // entity
{"FinishSpawningMD3Anim", (byte *)FinishSpawningMD3Anim},
#endif

In g_savestate.c about line 188 add :

	{"func_timer_use", (byte *)func_timer_use},
#ifdef SINGLEPLAYER // npc
{"trig_FinishSpawningNPC", (byte *)trig_FinishSpawningNPC},
#endif
#ifdef SINGLEPLAYER // entity
{"ActivateAnimModel", (byte *)ActivateAnimModel},
#endif

In g_savestate.c in function ReadEntity about line 820 add :

	// now copy the temp structure into the existing structure
memcpy( ent, &temp, size );
#ifdef SINGLEPLAYER // npc
//
// Fixup NPCs that are dead to show only last frame of animation
//
if(ent->npc && ent->health<=0)
{
ent->ns.ps.eFlags |= EF_FORCE_END_FRAME;
}
#endif
#ifdef SINGLEPLAYER // entity
//
// Fixup misc_model_anim with hidden end to not display again
//
if(Q_stricmp(ent->classname, "misc_model_anim")==0)
{
if(ent->s.modelindex2>0 && (ent->spawnflags & 2 ))
{
ent->s.eFlags |= EF_FORCE_END_FRAME;
}
}
#endif

In g_spawn.c about line 184 add :

void SP_misc_teleporter_dest (gentity_t *self);
void SP_misc_model(gentity_t *ent);
#ifdef SINGLEPLAYER // entity
void SP_misc_model_anim(gentity_t *ent);
#endif

void SP_misc_portal_camera(gentity_t *ent);

In g_spawn.c about line 275 add :

	{"misc_teleporter_dest", SP_misc_teleporter_dest},
{"misc_model", SP_misc_model},
#ifdef SINGLEPLAYER // entity
{"misc_model_anim", SP_misc_model_anim},
#endif

{"misc_portal_surface", SP_misc_portal_surface},

In cg_ents.c before the CG_Speaker function about line 198 add :

#ifdef SINGLEPLAYER // entity
static qboolean CG_ParseMD3AnimationFile( const char *filename, animation_t *anim ) {
char fname[MAX_QPATH];
int len;
char *token;
float fps;
char text[10000],*text_p;
fileHandle_t f;
static char err_parse[]="Error parsing ";
strcpy(fname,filename);
text_p=fname+strlen(fname);
while (*text_p!='.' && *text_p!='\\' && text_p!=fname)
text_p--;
if (*text_p=='.')
*text_p=0;
Q_strcat(fname,sizeof(fname),".anim");
// load the file
len = trap_FS_FOpenFile( fname, &f, FS_READ );
if ( len <= 0 ) {
CG_Printf( "File %s too short (exists?)\n", fname );
return qfalse;
}
if ( len >= sizeof( text ) - 1 ) {
CG_Printf( "File %s too long\n", fname );
return qfalse;
}
trap_FS_Read( text, len, f );
text[len] = 0;
trap_FS_FCloseFile( f );
// parse the text
text_p=text;
token = COM_Parse( &text_p );
if ( !*token ) {
CG_Printf("%s%s\n",fname);
return qfalse;
}
anim->firstFrame = atoi( token );
token = COM_Parse( &text_p );
if ( !*token ) {
CG_Printf("%s%s\n",fname);
return qfalse;
}
anim->numFrames = atoi( token );
anim->reversed = qfalse;
anim->flipflop = qfalse;
// if numFrames is negative the animation is reversed
if (anim->numFrames < 0) {
anim->numFrames = -anim->numFrames;
anim->reversed = qtrue;
}
token = COM_Parse( &text_p );
if ( !*token ) {
CG_Printf("%s%s\n",fname);
return qfalse;
}
anim->loopFrames = atoi( token );
token = COM_Parse( &text_p );
if ( !*token ) {
CG_Printf("%s%s\n",fname);
return qfalse;
}
fps = atof( token );
if ( fps == 0 ) {
fps = 1;
}
anim->frameLerp = 1000 / fps;
anim->initialLerp = 1000 / fps;
return qtrue;
}
static void CG_ModelAnimate( centity_t *cent, int *md3Old, int *md3, float *md3BackLerp ) {
entityState_t *s1;
lerpFrame_t *model=&cent->md3.model;
s1 = &cent->currentState;
if (cent->currentState.eFlags & EF_FORCE_END_FRAME ) {
cent->md3.state &= ~2;
cent->md3.state &= ~4;
}
if (s1->modelindex2!=cent->md3.lastCode) // are we being triggered from the server?
{
cent->md3.lastCode=s1->modelindex2;
if (!(cent->md3.state & 2)) // is it animating?
{
cent->md3.state |= 4; // show model
cent->md3.state |= 2; // if not, activate animation
cent->md3.state ^= ANIM_TOGGLEBIT;
CG_ClearLerpFrameNPC(&cent->md3.anim, model, cent->md3.state & ANIM_TOGGLEBIT);
}
}
if (cent->currentState.eFlags & EF_FORCE_END_FRAME ) {
model->frame = model->animation->firstFrame; // + model->animation->numFrames - 1;
model->oldFrame = model->frame;
model->backlerp = 0;
cent->md3.state &= ~2;
}
if (cent->md3.state & 2) // do we have to animate?
{
CG_RunLerpFrameNPC( &cent->md3.anim, model, cent->md3.state & ANIM_TOGGLEBIT, cent->md3.speed );
if (model->oldFrame==model->frame &&
model->frame==model->animation->numFrames+model->animation->firstFrame-1) // finished?
{
cent->md3.state &= ~2; // stop animation processing
if (s1->generic1 & 2) // dissapear if HIDDEN_END is turned on
cent->md3.state &= ~4;
}
}
*md3Old = model->oldFrame;
*md3 = model->frame;
*md3BackLerp = model->backlerp;
}
/*
==================
CG_ModelAnim
==================
*/
static void CG_ModelAnim( centity_t *cent ) {
refEntity_t ent;
entityState_t *s1;
s1 = &cent->currentState;
// if set to invisible, skip
if (!s1->modelindex) {
return;
}
if (!(cent->md3.state & 1)) // was animation config loaded?
{
const char *modelName;
modelName = CG_ConfigString( CS_MODELS+cg_entities[s1->number].currentState.modelindex );
if (!CG_ParseMD3AnimationFile(modelName,&cent->md3.anim))
{
// for debugging
cent->md3.anim.firstFrame=0;
cent->md3.anim.flipflop=0;
cent->md3.anim.frameLerp=100;
cent->md3.anim.initialLerp=100;
cent->md3.anim.loopFrames=10;
cent->md3.anim.numFrames=10;
cent->md3.anim.reversed=0;
}
cent->md3.state |= 1;
if (!(s1->generic1 & 1)) // show if HIDDEN_START is turned off
cent->md3.state|=4;

cent->md3.speed = (float)(s1->generic1 & 0xF0) / 16;
}
memset (&ent, 0, sizeof(ent));
CG_ModelAnimate(cent,&ent.oldframe,&ent.frame,&ent.backlerp);
if (!(cent->md3.state & 4)) // do nothing if visible flag is not set
return;
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
ent.hModel = cgs.gameModels[s1->modelindex];
// convert angles to axis
AnglesToAxis( cent->lerpAngles, ent.axis );
// add to refresh list
trap_R_AddRefEntityToScene (&ent);
}
#endif

In cg_ents.c in function CG_AddCEntity about line 1145 add :

#ifdef SINGLEPLAYER // entity
case ET_MODELANIM:
CG_ModelAnim( cent );
break;
#endif

case ET_PLAYER:
CG_Player( cent );
break;

In cg_local.h before struct centity_s about line 180 add :

#ifdef SINGLEPLAYER // entity
typedef struct {
int state;
lerpFrame_t model;
animation_t anim;
int lastCode;
float speed;
} md3Entity_t;
#endif

In cg_local.h in struct centity_s about line 208 add :

	playerEntity_t	pe;
#ifdef SINGLEPLAYER // npc
npcEntity_t ne;
#endif
#ifdef SINGLEPLAYER // entity
md3Entity_t md3;
#endif

int errorTime;

In cg_local.h about line 1424 add :

sfxHandle_t	CG_CustomSound( int clientNum, const char *soundName );
#ifdef SINGLEPLAYER // npc
void CG_RunLerpFrameNPC( animation_t *ai, lerpFrame_t *lf, int newAnimation, float speedScale );
#endif
#ifdef SINGLEPLAYER // entity
void CG_ClearLerpFrameNPC( animation_t *ai, lerpFrame_t *lf, int animationNumber ) ;
#endif

In cg_player.c before function CG_RunLerpFrame about line 1233 add :

#ifdef SINGLEPLAYER // entity
/*
===============
CG_ClearLerpFrame
===============
*/
void CG_ClearLerpFrameNPC( animation_t *ai, lerpFrame_t *lf, int animationNumber ) {
lf->frameTime = lf->oldFrameTime = cg.time;
CG_SetLerpFrameAnimationNPC( ai, lf, animationNumber );
lf->oldFrame = lf->frame = lf->animation->firstFrame;
}
#endif

If you have not made the code changes for the AI controlled characters then you must add the following just before the above code :

#ifdef SINGLEPLAYER // npc
static void CG_SetLerpFrameAnimationNPC( animation_t *ai, lerpFrame_t *lf, int newAnimation ) {
animation_t *anim;
lf->animationNumber = newAnimation;
newAnimation &= ~ANIM_TOGGLEBIT;
if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) {
CG_Error( "Bad animation number: %i", newAnimation );
}
anim = ai+newAnimation;
lf->animation = anim;
lf->animationTime = lf->frameTime + anim->initialLerp;
if ( cg_debugAnim.integer ) {
CG_Printf( "Anim: %i\n", newAnimation );
}
}
/*
===============
CG_RunLerpFrameNPC
Sets cg.snap, cg.oldFrame, and cg.backlerp
cg.time should be between oldFrameTime and frameTime after exit
===============
*/
void CG_RunLerpFrameNPC( animation_t *ai, lerpFrame_t *lf, int newAnimation, float speedScale ) {
int f,numFrames;
animation_t *anim;
// debugging tool to get no animations
if ( cg_animSpeed.integer == 0 ) {
lf->oldFrame = lf->frame = lf->backlerp = 0;
return;
}
// see if the animation sequence is switching
if ( newAnimation != lf->animationNumber || !lf->animation ) {
CG_SetLerpFrameAnimationNPC( ai, lf, newAnimation );
}
// if we have passed the current frame, move it to
// oldFrame and calculate a new frame
if ( cg.time >= lf->frameTime ) {
lf->oldFrame = lf->frame;
lf->oldFrameTime = lf->frameTime;
// get the next frame based on the animation
anim = lf->animation;
if ( !anim->frameLerp ) {
return; // shouldn't happen
}
if ( cg.time < lf->animationTime ) {
lf->frameTime = lf->animationTime; // initial lerp
} else {
lf->frameTime = lf->oldFrameTime + anim->frameLerp;
}
f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
f *= speedScale; // adjust for haste, etc
numFrames = anim->numFrames;
if (anim->flipflop) {
numFrames *= 2;
}
if ( f >= numFrames ) {
f -= numFrames;
if ( anim->loopFrames ) {
f %= anim->loopFrames;
f += anim->numFrames - anim->loopFrames;
} else {
f = numFrames - 1;
// the animation is stuck at the end, so it
// can immediately transition to another sequence
lf->frameTime = cg.time;
}
}
if ( anim->reversed ) {
lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
}
else if (anim->flipflop && f>=anim->numFrames) {
lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames);
}
else {
lf->frame = anim->firstFrame + f;
}
if ( cg.time > lf->frameTime ) {
lf->frameTime = cg.time;
if ( cg_debugAnim.integer ) {
CG_Printf( "Clamp lf->frameTime\n");
}
}
}
if ( lf->frameTime > cg.time + 200 ) {
lf->frameTime = cg.time;
}
if ( lf->oldFrameTime > cg.time ) {
lf->oldFrameTime = cg.time;
}
// calculate current lerp value
if ( lf->frameTime == lf->oldFrameTime ) {
lf->backlerp = 0;
} else {
lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
}
}
#endif

and add the following in cg_local.h about line 1424 :

sfxHandle_t	CG_CustomSound( int clientNum, const char *soundName );
#ifdef SINGLEPLAYER // npc
void CG_RunLerpFrameNPC( animation_t *ai, lerpFrame_t *lf, int newAnimation, float speedScale );
#endif


Return to Home Page