//----------------------------------------------------------------------------- // // $Logfile:: /EF2/Code/DLLs/game/animate.cpp $ // $Revision:: 23 $ // $Author:: Singlis $ // $Date:: 9/26/03 2:35p $ // // Copyright (C) 1998 by Ritual Entertainment, Inc. // All rights reserved. // // This source is may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // // DESCRIPTION: Animate Class // #include "_pch_cpp.h" #include "animate.h" #include "player.h" #include // Leg Animation events Event EV_Anim ( "anim", EV_DEFAULT, "s", "animName", "set the legs animation to animName." ); Event EV_SetFrame ( "setframe", EV_CODEONLY, "iS", "frameNumber animName", "Set the frame on the legs, if anim is not specified, current is assumed." ); Event EV_AnimDone ( "animdone", EV_CODEONLY, NULL, NULL, "Legs animation has finished, not for script use." ); Event EV_FrameDelta ( "setmovedelta", EV_CODEONLY, "v", "moveDelta", "movement from animation, not for script use." ); Event EV_StopAnimating ( "stopanimating", EV_CODEONLY, NULL, NULL, "stop the legs from animating. Animation will end at the end of current cycle." ); // Torso Animation events Event EV_Torso_Anim ( "torso_anim", EV_CODEONLY, "s", "animName", "set the torso animation to animName." ); Event EV_Torso_SetFrame ( "torso_setframe", EV_CODEONLY, "iS", "frameNumber animName", "Set the frame on the torso, if anim is not specified, current is assumed." ); Event EV_Torso_AnimDone ( "torso_animdone", EV_CODEONLY, NULL, NULL, "Torso animation has finished, not for script use." ); Event EV_Torso_StopAnimating ( "torso_stopanimating", EV_CODEONLY, NULL, NULL, "stop the torso from animating. Animation will end at the end of current cycle." ); Event EV_NewAnim ( "animate_newanim", EV_CODEONLY, "ii", "animNum bodyPart", "Start a new animation, not for script use." ); CLASS_DECLARATION( Listener, Animate, "animate" ) { { &EV_Anim, &Animate::Legs_AnimEvent }, { &EV_SetFrame, &Animate::Legs_SetFrameEvent }, { &EV_AnimDone, &Animate::Legs_AnimDoneEvent }, { &EV_StopAnimating, &Animate::Legs_StopAnimating }, { &EV_FrameDelta, &Animate::FrameDeltaEvent }, { &EV_NewAnim, &Animate::NewAnimEvent }, { &EV_Torso_Anim, &Animate::Torso_AnimEvent }, { &EV_Torso_SetFrame, &Animate::Torso_SetFrameEvent }, { &EV_Torso_AnimDone, &Animate::Torso_AnimDoneEvent }, { &EV_Torso_StopAnimating, &Animate::Torso_StopAnimating }, { NULL, NULL } }; Animate::Animate() { // Should always use other constructor assert( 0 ); } Animate::Animate( Entity *ent ) { // Animation variables frame_delta = Vector(0, 0, 0); animDoneEvent = NULL; torso_animDoneEvent = NULL; legs_animtime = 0; torso_animtime = 0; legs_starttime = 0; torso_starttime = 0; legs_numframes = 0; torso_numframes = 0; legs_frametime = 0; torso_frametime = 0; self = ent; if ( self ) { self->edict->s.animationRate = 1.0f; self->edict->s.anim |= (ANIM_SERVER_EXITCOMMANDS_PROCESSED); self->edict->s.torso_anim |= (ANIM_SERVER_EXITCOMMANDS_PROCESSED); self->edict->s.effectsAnims[ 0 ] = -1 ; self->edict->s.effectsAnims[ 1 ] = -1 ; self->edict->s.effectsAnims[ 2 ] = -1 ; self->edict->s.effectsAnims[ 3 ] = -1 ; } } Animate::~Animate() { if ( animDoneEvent ) { delete animDoneEvent; animDoneEvent = NULL; } if ( torso_animDoneEvent ) { delete torso_animDoneEvent; torso_animDoneEvent = NULL; } } void Animate::NewAnim( int animnum, bodypart_t part ) { // // this is an animating model, we set this here so that non-animating models // don't have to go through the animating code path // if ( self->edict->s.eType == ET_GENERAL ) { self->edict->s.eType = ET_MODELANIM; } if ( LoadingSavegame ) { // if we are loading a game, we don't need to start animating, that will be automatic // all these events would mess everything up anyway return; } int *anim; int flags; float *animtime; float *starttime; float *frametime; int *numframes; switch( part ) { case legs: anim = &self->edict->s.anim; self->edict->s.frame &= ~FRAME_EXPLICIT; flags = EVENT_LEGS_ANIM; animtime = &legs_animtime; starttime = &legs_starttime; frametime = &legs_frametime; numframes = &legs_numframes; break; case torso: anim = &self->edict->s.torso_anim; self->edict->s.torso_frame &= ~FRAME_EXPLICIT; flags = EVENT_TORSO_ANIM; animtime = &torso_animtime; starttime = &torso_starttime; frametime = &torso_frametime; numframes = &torso_numframes; break; default: warning( "NewAnim", "Unknown body part %d", part ); return; break; } int last_anim = *anim & ANIM_MASK; int last_anim_flags = *anim; float lastStartTime = *starttime; // // if the animations were different we need to process the entry and exit events // if ( ( last_anim != animnum ) && !( last_anim_flags & ANIM_SERVER_EXITCOMMANDS_PROCESSED ) ) { // exit the previous animation tiki_cmd_t cmds; if ( gi.Frame_Commands( self->edict->s.modelindex, last_anim, TIKI_FRAME_CMD_EXIT, &cmds ) ) { for( int ii = 0; ii < cmds.num_cmds; ii++ ) { Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); ev->SetSource( EV_FROM_ANIMATION ); ev->SetAnimationNumber( last_anim ); ev->SetAnimationFrame( 0 ); for( int j = 1; j < cmds.cmds[ ii ].num_args; j++ ) { ev->AddToken( cmds.cmds[ ii ].args[ j ] ); } self->ProcessEvent( ev ); } } } if ( ( animnum >= 0 ) && ( animnum < gi.NumAnims( self->edict->s.modelindex ) ) ) { if ( *starttime == level.time ) { // don't toggle the togglebit if we've already had an animation set this frame *anim = ( *anim & ANIM_TOGGLEBIT ) | animnum | ANIM_BLEND; } else { *anim = ( ( *anim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | animnum | ANIM_BLEND; } } else { // bad value return; } // get rid of old anim events CancelFlaggedEvents( flags ); self->CancelFlaggedEvents( flags ); // get total time of animation float totaltime = gi.Anim_Time( self->edict->s.modelindex, animnum ); totaltime /= self->edict->s.animationRate; // set the total time for the animation *animtime = totaltime; // set the start time of the animation *starttime = level.time; // set the number of frames for the animation *numframes = gi.Anim_NumFrames( self->edict->s.modelindex, animnum ); // set the time for each animation frame *frametime = gi.Frame_Time( self->edict->s.modelindex, animnum, 0 ); *frametime /= self->edict->s.animationRate; bool has_commands = (gi.Anim_HasCommands( self->edict->s.modelindex, animnum ) != 0); if ( self->edict->s.eFlags & EF_DONT_PROCESS_COMMANDS ) has_commands = false; if ( ( ( last_anim != animnum ) || ( lastStartTime == 0 ) ) && ( has_commands ) ) { ClearAllEffectAnims(); // enter this animation tiki_cmd_t cmds; if ( gi.Frame_Commands( self->edict->s.modelindex, animnum, TIKI_FRAME_CMD_ENTRY, &cmds ) ) { for( int ii = 0; ii < cmds.num_cmds; ii++ ) { Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); ev->SetSource( EV_FROM_ANIMATION ); ev->SetAnimationNumber( animnum ); ev->SetAnimationFrame( 0 ); for( int j = 1; j < cmds.cmds[ ii ].num_args; j++ ) { ev->AddToken( cmds.cmds[ ii ].args[ j ] ); } self->ProcessEvent( ev ); } } } // initially don't post move delta stuff bool dodelta = false; // if it is the legs, find out if there is a delta and if it is a delta_driven animation if ( part == legs ) { Vector totaldelta; gi.Anim_AbsoluteDelta( self->edict->s.modelindex, animnum, totaldelta ); float length = totaldelta.length(); if ( length > MINIMUM_DELTA_MOVEMENT ) { int flags = gi.Anim_Flags( self->edict->s.modelindex, animnum ); if ( !( flags & MDL_ANIM_DELTA_DRIVEN ) ) { length /= (float)*numframes; if ( length > MINIMUM_DELTA_MOVEMENT_PER_FRAME ) { dodelta = true; } } } } if ( has_commands || dodelta ) { float time = 0; for( int i = 0; i < *numframes; i++ ) { if ( has_commands ) { // we want normal frame commands to occur right on the frame tiki_cmd_t cmds; if ( gi.Frame_Commands( self->edict->s.modelindex, animnum, i, &cmds ) ) { for( int ii = 0; ii < cmds.num_cmds; ii++ ) { Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); ev->SetSource( EV_FROM_ANIMATION ); ev->SetAnimationNumber( animnum ); ev->SetAnimationFrame( i ); for( int j = 1; j < cmds.cmds[ ii ].num_args; j++ ) { ev->AddToken( cmds.cmds[ ii ].args[ j ] ); } self->PostEvent( ev, time, flags ); } } } // add to time time += *frametime; // we want deltas to occur at the end of the frame // only add deltas on the legs if ( dodelta ) { Vector delta; // get the current frame delta gi.Frame_Delta( self->edict->s.modelindex, animnum, i, delta ); if ( *frametime > FRAMETIME ) { float time_offset; VectorScale( delta, ( FRAMETIME / *frametime ), delta ); for ( time_offset = 0; time_offset < *frametime; time_offset += FRAMETIME ) { Event *ev = new Event( EV_FrameDelta ); ev->AddVector( delta ); PostEvent( ev, time + time_offset, flags ); } } else { Event *ev = new Event( EV_FrameDelta ); ev->AddVector( delta ); ev->AddFloat( *frametime ); PostEvent( ev, time, flags ); } } } } // // if we have a 1 frame animation, which has no commands, // we aren't a subclass of Sentient and our animation time is the same as frametime // there is no reason for us to constantly animate, since nothing will change // lets get the hell out of dodge! if ( ( *numframes == 1 ) && !self->isSubclassOf( Player ) && !has_commands && ( *frametime <= FRAMETIME ) ) { switch( part ) { case legs: if ( animDoneEvent ) { self->PostEvent( *animDoneEvent, 0.0f ); } break; case torso: if ( torso_animDoneEvent ) { self->PostEvent( *torso_animDoneEvent, 0.0f ); } break; default: break; } return; } // Note: Below we post the ANIMDONE on the last frame of the animation if we're switching // animations. If we're playing a looped anim, the ANIMDONE event is posted one frame early. // No idea why it makes a difference, but it removes hitching on the last frame of a looped anim. switch( part ) { case legs: if ( totaltime > *frametime && last_anim == animnum ) PostEvent( EV_AnimDone, totaltime - *frametime, flags ); else PostEvent( EV_AnimDone, totaltime, flags ); break; case torso: if ( totaltime > *frametime && last_anim == animnum ) PostEvent( EV_Torso_AnimDone, totaltime - *frametime, flags ); else PostEvent( EV_Torso_AnimDone, totaltime, flags ); break; default: warning( "NewAnim", "Unknown body part %d", part ); return; break; } } void Animate::NewAnim( int animnum, Event &newevent, bodypart_t part ) { SetAnimDoneEvent( newevent, part ); NewAnim( animnum, part ); } void Animate::NewAnim( int animnum, Event *newevent, bodypart_t part ) { SetAnimDoneEvent( newevent, part ); NewAnim( animnum, part ); } void Animate::FrameDeltaEvent( Event *ev ) { frame_delta = ev->GetVector( 1 ); self->total_delta += frame_delta * self->edict->s.scale ; } void Animate::EndAnim( bodypart_t part ) { Event * ev; switch( part ) { case legs: if ( animDoneEvent ) { self->PostEvent( *animDoneEvent, 0.0f ); } ev = new Event( EV_NewAnim ); ev->AddInteger( self->edict->s.anim ); ev->AddInteger( part ); if ( legs_animtime > legs_frametime ) PostEvent( ev, legs_frametime, EVENT_LEGS_ANIM ); else PostEvent( ev, 0.0f, EVENT_LEGS_ANIM ); break; case torso: if ( torso_animDoneEvent ) { self->PostEvent( *torso_animDoneEvent, 0.0f ); } ev = new Event( EV_NewAnim ); ev->AddInteger( self->edict->s.torso_anim ); ev->AddInteger( part ); if ( torso_animtime > torso_frametime ) PostEvent( ev, torso_frametime, EVENT_TORSO_ANIM ); else PostEvent( ev, 0.0f, EVENT_TORSO_ANIM ); break; default: warning( "EndAnim", "Unknown body part %d", part ); return; break; } } void Animate::SetAnimDoneEvent( Event *event, bodypart_t part ) { Event **doneevent; switch( part ) { case legs: doneevent = &animDoneEvent; break; case torso: doneevent = &torso_animDoneEvent; break; default: warning( "SetAnimDoneEvent", "Unknown body part %d", part ); return; break; } if ( *doneevent ) { delete *doneevent; } *doneevent = event; } void Animate::SetAnimDoneEvent( const Event &event, bodypart_t part ) { SetAnimDoneEvent( new Event( event ), part ); } str Animate::GetName() { return currentAnim; } //=============================================================== // Name: AddEffectAnim // Class: Animate // // Description: Add an effects anim to be layered about a normal // anim. // // Parameters: const char* -- the anim to whose effects you want to add. // // Returns: None // //=============================================================== void Animate::AddEffectAnim( const char *animname ) { int num = gi.Anim_Random( self->edict->s.modelindex, animname ); if ( num != -1 ) { for ( int effectAnimIdx = 0; effectAnimIdx < NUM_EFFECTS_ANIMS; ++effectAnimIdx ) { int effectAnimNum = self->edict->s.effectsAnims[ effectAnimIdx ] ; if ( effectAnimNum == num ) return ; if ( effectAnimNum == -1 ) { self->edict->s.effectsAnims[ effectAnimIdx ] = num ; return ; } } } } //=============================================================== // Name: RemoveEffectAnim // Class: Animate // // Description: Removes an anim effect // // Parameters: const char *animname // // Returns: None // //=============================================================== void Animate::RemoveEffectAnim( const char *animname ) { int num = gi.Anim_Random( self->edict->s.modelindex, animname ); if ( num != -1 ) { for ( int effectAnimIdx = 0; effectAnimIdx < NUM_EFFECTS_ANIMS; ++effectAnimIdx ) { if ( self->edict->s.effectsAnims[ effectAnimIdx ] == num ) { self->edict->s.effectsAnims[ effectAnimIdx ] = -1 ; } } } } //=============================================================== // Name: ClearAllEffectAnims // Class: Animate // // Description: Clears the list of effect anims. // // Parameters: None // // Returns: None // //=============================================================== void Animate::ClearAllEffectAnims( void ) { for ( int effectAnimIdx = 0; effectAnimIdx < NUM_EFFECTS_ANIMS; ++effectAnimIdx ) { self->edict->s.effectsAnims[ effectAnimIdx ] = -1 ; } } void Animate::RandomAnimate( const char *animname, Event *endevent, bodypart_t part ) { Event *event_to_post; int num; int flags; qboolean allparts; assert( animname ); if ( !animname ) { return; } allparts = false; if ( part == all ) { allparts = true; } do { if ( allparts ) { switch( part ) { case all: part = legs; break; case legs: part = torso; break; case torso: return; break; } } switch( part ) { case legs: flags = EVENT_LEGS_ANIM; break; case torso: flags = EVENT_TORSO_ANIM; break; default: warning( "RandomAnimate", "Unknown body part %d", part ); return; break; } num = gi.Anim_Random( self->edict->s.modelindex, animname ); currentAnim = animname; // // this is here to ensure that multiple distinct events are posted for each body part // if ( allparts && ( part != legs ) ) { if ( endevent ) { event_to_post = new Event( endevent ); } else { event_to_post = NULL; } } else { event_to_post = endevent; } // // see if we even have a valid animation at all // if ( num == -1 ) { if ( event_to_post ) { self->PostEvent( event_to_post, FRAMETIME, flags ); } } else { SetAnimDoneEvent( event_to_post, part ); NewAnim( num, part ); } } while( allparts ); } void Animate::SetAnimationRate( const float animationRate ) { oldAnimationRate = self->edict->s.animationRate; self->edict->s.animationRate = animationRate; } void Animate::RestoreAnimationRate( void ) { self->edict->s.animationRate = oldAnimationRate; } void Animate::SetFrame( int framenum, bodypart_t part, int anim ) { int flags; int numframes; int *panim; int *frame; switch( part ) { case legs: frame = &self->edict->s.frame; flags = EVENT_LEGS_ANIM; numframes = legs_numframes; panim = &self->edict->s.anim; break; case torso: frame = &self->edict->s.torso_frame; flags = EVENT_TORSO_ANIM; numframes = torso_numframes; panim = &self->edict->s.torso_anim; break; default: warning( "SetFrame", "Unknown body part %d", part ); return; break; } if ( anim >= 0 ) { numframes = gi.Anim_NumFrames( self->edict->s.modelindex, anim ); } if ( framenum < 0 || ( framenum >= numframes ) ) { warning( "SetFrame","Frame %d is out of range on model %s", framenum, self->model.c_str() ); return; } // get rid of old anim events so we don't animate CancelFlaggedEvents( flags ); self->CancelFlaggedEvents( flags ); *frame = framenum | FRAME_EXPLICIT; // if we have a frame override, make sure to set the animation as well if ( anim >= 0 ) { *panim = anim | ANIM_BLEND; } } qboolean Animate::HasAnim( const char *animname ) { int num; num = gi.Anim_Random( self->edict->s.modelindex, animname ); return ( num >= 0 ); } void Animate::NewAnimEvent( Event *ev ) { NewAnim( ev->GetInteger( 1 ) & ANIM_MASK, (bodypart_t) ev->GetInteger( 2 ) ); } void Animate::StopAnimating( bodypart_t part ) { int frame; int anim; if ( part == all ) { // legs frame = CurrentFrame( legs ); anim = CurrentAnim( legs ); SetFrame( frame, legs, anim ); // torso frame = CurrentFrame( torso ); anim = CurrentAnim( torso ); SetFrame( frame, torso, anim ); } else { frame = CurrentFrame( part ); anim = CurrentAnim( part ); SetFrame( frame, part, anim ); } } void Animate::StopAnimatingAtEnd( bodypart_t part ) { int anim; if ( part == all ) { StopAnimatingAtEnd( legs ); StopAnimatingAtEnd( torso ); } else if ( part == legs ) { anim = CurrentAnim( part ); SetFrame( legs_numframes - 1, part, anim ); } else if ( part == torso ) { anim = CurrentAnim( part ); SetFrame( torso_numframes - 1, part, anim ); } } //////////////////////////// // // BODY PART SPECIFIC EVENTS // //////////////////////////// // Legs void Animate::Legs_AnimDoneEvent( Event * ) { EndAnim( legs ); } void Animate::Legs_AnimEvent( Event *ev ) { RandomAnimate( ev->GetString( 1 ), NULL, legs ); } void Animate::Legs_SetFrameEvent( Event *ev ) { int framenum; int animnum; framenum = ev->GetInteger( 1 ); if ( ev->NumArgs() > 1 ) { animnum = gi.Anim_NumForName( self->edict->s.modelindex, ev->GetString( 2 ) ); } else { animnum = -1; } SetFrame( framenum, legs, animnum ); } // HACK HACK HACK void Animate::Legs_StopAnimating( Event * ) { CancelFlaggedEvents( EVENT_LEGS_ANIM ); self->CancelFlaggedEvents( EVENT_LEGS_ANIM ); } // Torso void Animate::Torso_AnimDoneEvent( Event * ) { EndAnim( torso ); } void Animate::Torso_AnimEvent( Event *ev ) { RandomAnimate( ev->GetString( 1 ), NULL, torso ); } void Animate::Torso_SetFrameEvent( Event *ev ) { int framenum; int animnum; framenum = ev->GetInteger( 1 ); if ( ev->NumArgs() > 1 ) { animnum = gi.Anim_NumForName( self->edict->s.modelindex, ev->GetString( 2 ) ); } else { animnum = -1; } SetFrame( framenum, torso, animnum ); } // HACK HACK HACK void Animate::Torso_StopAnimating( Event * ) { CancelFlaggedEvents( EVENT_TORSO_ANIM ); self->CancelFlaggedEvents( EVENT_TORSO_ANIM ); } void Animate::ClearTorsoAnim( void ) { tiki_cmd_t cmds; int last_anim; static qboolean clearing = false; last_anim = self->edict->s.torso_anim & ANIM_MASK; if ( ( self->edict->s.torso_anim & ANIM_BLEND ) && !( self->edict->s.torso_anim & ANIM_SERVER_EXITCOMMANDS_PROCESSED ) && !clearing ) { if ( gi.Frame_Commands( self->edict->s.modelindex, last_anim, TIKI_FRAME_CMD_EXIT, &cmds ) ) { int ii, j; clearing = true; for( ii = 0; ii < cmds.num_cmds; ii++ ) { Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); ev->SetSource( EV_FROM_ANIMATION ); ev->SetAnimationNumber( last_anim ); ev->SetAnimationFrame( 0 ); for( j = 1; j < cmds.cmds[ ii ].num_args; j++ ) { ev->AddToken( cmds.cmds[ ii ].args[ j ] ); } self->ProcessEvent( ev ); } clearing = false; } self->edict->s.torso_anim |= ANIM_SERVER_EXITCOMMANDS_PROCESSED; } CancelFlaggedEvents( EVENT_TORSO_ANIM ); self->CancelFlaggedEvents( EVENT_TORSO_ANIM ); self->edict->s.torso_anim &= ~ANIM_BLEND; } void Animate::ClearLegsAnim( void ) { tiki_cmd_t cmds; int last_anim; //static qboolean clearing = false; if ( self->edict->s.anim & ANIM_SERVER_EXITCOMMANDS_PROCESSED ) { last_anim = self->edict->s.anim & ANIM_MASK; if ( gi.Frame_Commands( self->edict->s.modelindex, last_anim, TIKI_FRAME_CMD_EXIT, &cmds ) ) { int ii, j; //clearing = true; for( ii = 0; ii < cmds.num_cmds; ii++ ) { Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); ev->SetSource( EV_FROM_ANIMATION ); ev->SetAnimationNumber( last_anim ); ev->SetAnimationFrame( 0 ); for( j = 1; j < cmds.cmds[ ii ].num_args; j++ ) { ev->AddToken( cmds.cmds[ ii ].args[ j ] ); } self->ProcessEvent( ev ); } //clearing = false; } self->edict->s.anim |= ANIM_SERVER_EXITCOMMANDS_PROCESSED; } CancelFlaggedEvents( EVENT_LEGS_ANIM ); self->CancelFlaggedEvents( EVENT_LEGS_ANIM ); self->edict->s.anim &= ~ANIM_BLEND; }