quake2-zaero/z_anim.c

706 lines
19 KiB
C

#if defined(_DEBUG) && defined(_Z_TESTMODE)
#include "g_local.h"
/*=========================================================================
Local functions.
=========================================================================*/
anim_data_t *anim_player_create(edict_t *monster);
void anim_player_think(edict_t *anim_player);
void update_directions(anim_data_t *data);
void update_frame_buffer(anim_data_t *data);
void calculate_buffer_actuals(anim_data_t *data);
/*=========================================================================
Local variables.
=========================================================================*/
#define MAX_ANIMATIONS 10
anim_data_t *animations[MAX_ANIMATIONS]; //record of all animations
int animations_count = 0;
/**************************************************************************
Misc routines.
**************************************************************************/
qboolean cut_up_string(char **str, char **clipping)
{
char *end;
while (**str == ' ')
(*str)++;
if(**str == '\0')
{
*clipping = NULL;
return false;
}
end = (*str) + 1;
while (*end != '\0')
{
if(*end == ' ')
{
*end = '\0';
end++;
break;
}
end++;
}
*clipping = gi.TagMalloc(strlen(*str) + 1, TAG_LEVEL);
strcpy(*clipping, *str);
*str = end;
return true;
}
edict_t *find_targetname(char *targetname)
{
int i;
for(i=0;i<globals.num_edicts;i++)
{
if(!g_edicts[i].targetname)
continue;
if(Q_stricmp(g_edicts[i].targetname, targetname) == 0)
return g_edicts + i;
}
gi.dprintf("name <%s> not found\n", targetname);
return NULL;
}
static edict_t *the_client = NULL;
edict_t *find_client(void)
{
int i;
if(the_client)
return the_client;
if(!maxclients->value)
return NULL;
for(i=1;i<globals.num_edicts;i++)
{
if(!g_edicts[i].inuse)
continue;
if(g_edicts[i].client)
{
the_client = g_edicts + i;
break;
}
}
return the_client;
}
/**************************************************************************
Exported aim correction routine.
**************************************************************************/
anim_data_t *find_monster_animator(edict_t *monster)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(anim->monster == monster)
return anim;
}
return NULL;
}
qboolean anim_player_correct_aim(edict_t *self, vec3_t aim)
{
anim_data_t *anim;
if(self->extra_data != animations)
return false;
anim = find_monster_animator(self);
if(!anim)
return false;
VectorCopy(anim->v_aim, aim);
return true;
}
/**************************************************************************
Methods and events for creating animation player edict and for coaxing
monsters to behave correctly.
**************************************************************************/
/*=========================================================================
Replacement black monster events.
=========================================================================*/
void no_pain(edict_t *self, edict_t *other, float kick, int damage)
{
return;
}
void no_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
return;
}
void no_happen(edict_t *self)
{
return;
}
/*=========================================================================
Construction methods.
=========================================================================*/
anim_data_t *anim_data_create(edict_t *monster)
{
anim_data_t *data;
if(!z_frame_get_sequence(monster->classname))
return NULL;
data = gi.TagMalloc(sizeof(anim_data_t), TAG_LEVEL);
data->monster = monster;
monster->extra_data = animations;
data->monster_frames[0].aifunc = NULL;
data->monster_frames[0].dist = 0.0;
data->monster_frames[0].thinkfunc = NULL;
data->monster_move.firstframe = 0;
data->monster_move.lastframe = 0;
data->monster_move.frame = data->monster_frames;
data->monster_move.endfunc = NULL;
data->monster_sequences = z_frame_get_sequence(monster->classname);
data->current_sequence = 0;
data->current_frame = 0;
data->last_dist = 0.0;
data->moving_forward = true;
data->paused = true;
data->stationary = true;
data->frame_events = false;
data->active = true;
data->facing = DIR_FIXED;
data->aim = DIR_FIXED;
return data;
}
anim_data_t *anim_player_create(edict_t *monster)
{
anim_data_t *data;
edict_t *anim_player;
data = anim_data_create(monster);
if(!data)
return data;
monster->monsterinfo.currentmove = &(data->monster_move);
monster->pain = no_pain;
monster->die = no_die;
monster->monsterinfo.stand = no_happen;
monster->monsterinfo.walk = no_happen;
monster->monsterinfo.run = no_happen;
monster->monsterinfo.dodge = NULL;
monster->monsterinfo.attack = no_happen;
monster->monsterinfo.melee = NULL;
monster->monsterinfo.sight = NULL;
monster->monsterinfo.idle = NULL;
anim_player = G_Spawn();
anim_player->extra_data = data;
anim_player->think = anim_player_think;
anim_player->nextthink = level.time + 0.1;
return data;
}
/*=========================================================================
Animation player behaviour routines.
=========================================================================*/
void advance_anim_frame(anim_data_t *anim, int count)
{
mmove_t *seq;
anim->moving_forward = (count < 0)? false : true;
anim->current_frame += count;
if(anim->current_sequence)
{
seq = anim->monster_sequences[anim->current_sequence - 1];
anim->current_frame += seq->lastframe - seq->firstframe + 1;
anim->current_frame %= seq->lastframe - seq->firstframe + 1;
}
}
void anim_player_think(edict_t *anim_player)
{
anim_data_t *data;
data = (anim_data_t *)anim_player->extra_data;
update_directions(data);
if(!data->paused)
advance_anim_frame(data, 1);
calculate_buffer_actuals(data);
update_frame_buffer(data);
anim_player->nextthink = level.time + 0.1;
}
/**************************************************************************
Rountines to make monster follow the frame buffer, ideal_yaw and
ideal_aim.
**************************************************************************/
void update_directions(anim_data_t *data)
{
edict_t *client = find_client();
vec3_t ang;
vec3_t point;
switch(data->facing)
{
case DIR_AT_CLIENT:
VectorSubtract(client->s.origin, data->monster->s.origin, point);
VectorNormalize(point);
vectoangles(point, ang);
data->monster->ideal_yaw = ang[YAW];
break;
case DIR_PARA_CLIENT:
data->monster->ideal_yaw = client->client->v_angle[YAW];
break;
}
switch(data->aim)
{
case DIR_AT_CLIENT:
VectorSubtract(client->s.origin, data->monster->s.origin, point);
VectorNormalize(point);
vectoangles(point, data->v_aim);
break;
case DIR_PARA_CLIENT:
AngleVectors(client->client->v_angle, data->v_aim, NULL, NULL);
break;
}
}
/*=========================================================================
Animator ai routine to move and track yaw.
=========================================================================*/
void ai_animator(edict_t *self, float dist)
{
if(dist != 0.0)
M_walkmove (self, self->s.angles[YAW], dist);
M_ChangeYaw(self);
}
/*=========================================================================
Routine to load the current actual_* data into the frame buffer.
=========================================================================*/
void update_frame_buffer(anim_data_t *data)
{
// defaults
data->monster_frames[0].aifunc = NULL;
data->monster_frames[0].dist = 0.0;
data->monster_frames[0].thinkfunc = NULL;
data->monster_move.firstframe = data->actual_frame;
data->monster_move.lastframe = data->actual_frame;
if(data->last_actual_frame != data->actual_frame)
{
if(!data->stationary)
{
if(data->moving_forward)
{
data->monster_frames[0].dist =
data->actual_sequence->frame[data->actual_sequence_idx].dist;
data->last_dist = data->monster_frames[0].dist;
}
else
{
data->monster_frames[0].dist = -data->last_dist;
data->last_dist = data->actual_sequence->frame[data->actual_sequence_idx].dist;
}
data->monster_frames[0].aifunc = ai_animator;
}
if(data->frame_events)
data->monster_frames[0].thinkfunc =
data->actual_sequence->frame[data->actual_sequence_idx].thinkfunc;
}
data->last_actual_frame = data->actual_frame;
}
/**************************************************************************
Routines to calculate actual frames to show.
**************************************************************************/
int get_total_frame_count(anim_data_t *data)
{
int total_frames = 0;
mmove_t **seq;
seq = data->monster_sequences;
while(*seq)
{
total_frames += (*seq)->lastframe - (*seq)->firstframe + 1;
seq++;
}
return total_frames;
}
/*=========================================================================
Frame selection routine. Note that sequence 0 has the special meaning of
cycling through all sequences on after another.
This routine sets the actual_frame and actual_sequence from current_frame
and current_sequence.
Assumptions:
- current_sequence = 0, means all frames with current_frame looping
- current_sequence > 0, a valid sequence and valid current_frame
=========================================================================*/
void calculate_buffer_actuals(anim_data_t *data)
{
int seq_frames, idx;
if(data->current_sequence)
{
data->actual_sequence = data->monster_sequences[data->current_sequence - 1];
data->actual_sequence_idx = data->current_frame;
/*data->actual_frame %= actual_sequence->lastframe -
actual_sequence->firstframe + 1;*/
}
else
{
data->current_frame %= get_total_frame_count(data);
data->actual_sequence_idx = data->current_frame;
idx = 0;
data->actual_sequence = data->monster_sequences[idx];
seq_frames = data->actual_sequence->lastframe -
data->actual_sequence->firstframe + 1;
while(data->actual_sequence_idx + 1 > seq_frames)
{
idx++;
data->actual_sequence_idx -= seq_frames;
data->actual_sequence = data->monster_sequences[idx];
seq_frames = data->actual_sequence->lastframe -
data->actual_sequence->firstframe + 1;
}
}
data->actual_frame = data->actual_sequence->firstframe +
data->actual_sequence_idx;
}
/**************************************************************************
Console command routines.
**************************************************************************/
void anim_player_report(char *targetname, char *description, qboolean on)
{
gi.dprintf("%s %s ", targetname, description);
if(on)
gi.dprintf("ON\n");
else
gi.dprintf("OFF\n");
}
/*=========================================================================
Advances count frames in the current sequence
=========================================================================*/
void anim_player_advance_frame(int count)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
advance_anim_frame(anim, count);
}
}
/*=========================================================================
Advances count sequences.
=========================================================================*/
void anim_player_advance_sequence(int count)
{
anim_data_t *anim;
int i, tcount;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
tcount = count;
while(tcount > 0)
{
tcount--;
(anim->current_sequence)++;
if(!anim->monster_sequences[anim->current_sequence - 1])
anim->current_sequence = 0;
}
while(tcount < 0)
{
(anim->current_sequence)--;
if(anim->current_sequence < 0)
{
anim->current_sequence = 0;
while(anim->monster_sequences[anim->current_sequence])
(anim->current_sequence)++;
}
tcount++;
}
anim->current_frame = 0;
}
}
/*=========================================================================
Set facing flag.
=========================================================================*/
void anim_player_set_facing(anim_dir_t facing)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
anim->facing = facing;
}
}
/*=========================================================================
Set aim flag.
=========================================================================*/
void anim_player_set_aim(anim_dir_t aim)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
anim->aim = aim;
}
}
/*=========================================================================
Toggles current monster event playing.
=========================================================================*/
void anim_player_events(void)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
anim->frame_events = !anim->frame_events;
anim_player_report(anim->monster->targetname, "frame events",
anim->frame_events);
}
}
/*=========================================================================
Toggles current monster stationary.
=========================================================================*/
void anim_player_still(void)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
anim->stationary = !anim->stationary;
anim_player_report(anim->monster->targetname, "stationary",
anim->stationary);
}
}
/*=========================================================================
Toggles current monster pause.
=========================================================================*/
void anim_player_pause(void)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if(!anim->active)
continue;
anim->paused = !anim->paused;
anim_player_report(anim->monster->targetname, "pause",
anim->paused);
}
}
/*=========================================================================
Captures a monster or sets the monster current if already captured.
=========================================================================*/
qboolean anim_player_capture(char *targetname)
{
edict_t *ent;
int i;
anim_data_t *anim;
//make sure we don't already have this one
for(i=0;i<animations_count;i++)
{
if(Q_stricmp(animations[i]->monster->targetname, targetname) == 0)
{
gi.dprintf("Target <%s> already captured\n", targetname);
return false;
}
}
//make sure we have room to hold the animation reference.
if(animations_count == MAX_ANIMATIONS)
{
gi.dprintf("Maximum of %d animations already used\n", MAX_ANIMATIONS);
return false;
}
ent = find_targetname(targetname);
if(!ent)
return false;
anim = anim_player_create(ent);
if(anim)
{
animations[animations_count++] = anim;
gi.dprintf("Target <%s> captured\n", targetname);
}
else
gi.dprintf("Target <%s> NOT captured\n", targetname);
return true;
}
/*=========================================================================
Toggles current monster stationary.
=========================================================================*/
void anim_player_set_active(char *targetname, qboolean active)
{
anim_data_t *anim;
int i;
for(i=0;i<animations_count;i++)
{
anim = animations[i];
if((Q_stricmp(anim->monster->targetname, targetname) == 0) ||
(Q_stricmp("all", targetname) == 0))
anim->active = active;
}
}
/*=========================================================================
Animation player command entry point.
Called in g_cmds.c
=========================================================================*/
void anim_player_cmd(edict_t *ent)
{
char *args, *arg1=NULL, *arg2=NULL;
args = gi.args();
if(!cut_up_string(&args, &arg1))
return;
cut_up_string(&args, &arg2);
// string switch
if(Q_stricmp (arg1, "capture") == 0)
anim_player_capture(arg2);
else if(Q_stricmp (arg1, "activate") == 0)
anim_player_set_active(arg2, true);
else if(Q_stricmp (arg1, "deactivate") == 0)
anim_player_set_active(arg2, false);
else if(Q_stricmp (arg1, "pause") == 0)
anim_player_pause();
else if(Q_stricmp (arg1, "still") == 0)
anim_player_still();
else if(Q_stricmp (arg1, "events") == 0)
anim_player_events();
else if(Q_stricmp (arg1, "s_next") == 0)
anim_player_advance_sequence(1);
else if(Q_stricmp (arg1, "s_prior") == 0)
anim_player_advance_sequence(-1);
else if(Q_stricmp (arg1, "s_reset") == 0)
anim_player_advance_sequence(0);
else if(Q_stricmp (arg1, "f_next") == 0)
anim_player_advance_frame(1);
else if(Q_stricmp (arg1, "f_prior") == 0)
anim_player_advance_frame(-1);
else if(Q_stricmp (arg1, "face_client") == 0)
anim_player_set_facing(DIR_AT_CLIENT);
else if(Q_stricmp (arg1, "face_para") == 0)
anim_player_set_facing(DIR_PARA_CLIENT);
else if(Q_stricmp (arg1, "face_fixed") == 0)
anim_player_set_facing(DIR_FIXED);
else if(Q_stricmp (arg1, "aim_client") == 0)
anim_player_set_aim(DIR_AT_CLIENT);
else if(Q_stricmp (arg1, "aim_para") == 0)
anim_player_set_aim(DIR_PARA_CLIENT);
else if(Q_stricmp (arg1, "aim_fixed") == 0)
anim_player_set_aim(DIR_FIXED);
else
gi.dprintf("unknown anim command <%s>\n", arg1);
//clean up
gi.TagFree(arg1);
if(arg2)
gi.TagFree(arg2);
}
#endif