//NOTE NOTE NOTE NOTE NOTE NOTE //I want to keep this function BG too, because it's fairly generic already, and it //would be nice to have proper prediction of animations. -rww // This function makes sure that the rider's in this vehicle are properly animated. void AnimateRiders( Vehicle_t *pVeh ) { animNumber_t Anim = BOTH_VS_IDLE; float fSpeedPercToMax; int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; playerState_t *pilotPS; playerState_t *parentPS; int curTime; // Boarding animation. if ( pVeh->m_iBoarding != 0 ) { // We've just started moarding, set the amount of time it will take to finish moarding. if ( pVeh->m_iBoarding < 0 ) { int iAnimLen; // Boarding from left... if ( pVeh->m_iBoarding == -1 ) { Anim = BOTH_VS_MOUNT_L; } else if ( pVeh->m_iBoarding == -2 ) { Anim = BOTH_VS_MOUNT_R; } else if ( pVeh->m_iBoarding == -3 ) { Anim = BOTH_VS_MOUNTJUMP_L; } else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_LEFT) { iBlend = 0; Anim = BOTH_VS_MOUNTTHROW_R; } else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_RIGHT) { iBlend = 0; Anim = BOTH_VS_MOUNTTHROW_L; } // Set the delay time (which happens to be the time it takes for the animation to complete). // NOTE: Here I made it so the delay is actually 40% (0.4f) of the animation time. #ifdef _JK2MP iAnimLen = BG_AnimLength( pVeh->m_pPilot->localAnimIndex, Anim ) * 0.4f; pVeh->m_iBoarding = BG_GetTime() + iAnimLen; #else iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, Anim );// * 0.4f; if (pVeh->m_iBoarding!=VEH_MOUNT_THROW_LEFT && pVeh->m_iBoarding!=VEH_MOUNT_THROW_RIGHT) { pVeh->m_iBoarding = level.time + (iAnimLen*0.4f); } else { pVeh->m_iBoarding = level.time + iAnimLen; } #endif // Set the animation, which won't be interrupted until it's completed. // TODO: But what if he's killed? Should the animation remain persistant??? iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; #ifdef _JK2MP BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, SETANIM_BOTH, Anim, iFlags, iBlend); #else NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); if (pVeh->m_pOldPilot) { iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, BOTH_VS_MOUNTTHROWEE); NPC_SetAnim( pVeh->m_pOldPilot, SETANIM_BOTH, BOTH_VS_MOUNTTHROWEE, iFlags, iBlend ); } #endif } #ifndef _JK2MP if (pVeh->m_pOldPilot && pVeh->m_pOldPilot->client->ps.torsoAnimTimer<=0) { if (Q_irand(0, player->count)==0) { player->count++; player->lastEnemy = pVeh->m_pOldPilot; G_StartMatrixEffect(player, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); } gentity_t* oldPilot = pVeh->m_pOldPilot; pVeh->m_pVehicleInfo->Eject(pVeh, pVeh->m_pOldPilot, qtrue); // will set pointer to zero // Kill Him //---------- oldPilot->client->noRagTime = -1; // no ragdoll for you G_Damage(oldPilot, pVeh->m_pPilot, pVeh->m_pPilot, pVeh->m_pPilot->currentAngles, pVeh->m_pPilot->currentOrigin, 1000, 0, MOD_CRUSH); // Compute THe Throw Direction As Backwards From The Vehicle's Velocity //---------------------------------------------------------------------- vec3_t throwDir; VectorScale(pVeh->m_pParentEntity->client->ps.velocity, -1.0f, throwDir); VectorNormalize(throwDir); throwDir[2] += 0.3f; // up a little // Now Throw Him Out //------------------- G_Throw(oldPilot, throwDir, VectorLength(pVeh->m_pParentEntity->client->ps.velocity)/10.0f); NPC_SetAnim(oldPilot, SETANIM_BOTH, BOTH_DEATHBACKWARD1, SETANIM_FLAG_OVERRIDE, iBlend ); } #endif return; } #ifdef _JK2MP //fixme if (1) return; #endif #ifdef _JK2MP pilotPS = pVeh->m_pPilot->playerState; parentPS = pVeh->m_pPilot->playerState; #else pilotPS = &pVeh->m_pPilot->client->ps; parentPS = &pVeh->m_pParentEntity->client->ps; #endif #ifndef _JK2MP//SP curTime = level.time; #elif QAGAME//MP GAME curTime = level.time; #elif CGAME//MP CGAME //FIXME: pass in ucmd? Not sure if this is reliable... curTime = pm->cmd.serverTime; #endif // Percentage of maximum speed relative to current speed. fSpeedPercToMax = parentPS->speed / pVeh->m_pVehicleInfo->speedMax; // Going in reverse... #ifdef _JK2MP if ( pVeh->m_ucmd.forwardmove < 0 && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) #else if ( fSpeedPercToMax < -0.018f && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) #endif { Anim = BOTH_VS_REV; iBlend = 500; } else { bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); #ifdef _JK2MP //fixme: flying tends to spaz out a lot bool Flying = false; bool Crashing = false; #else bool Flying = !!(pVeh->m_ulFlags & VEH_FLYING); bool Crashing = !!(pVeh->m_ulFlags & VEH_CRASHING); #endif bool Right = (pVeh->m_ucmd.rightmove>0); bool Left = (pVeh->m_ucmd.rightmove<0); bool Turbo = (curTime<pVeh->m_iTurboTime); EWeaponPose WeaponPose = WPOSE_NONE; // Remove Crashing Flag //---------------------- pVeh->m_ulFlags &= ~VEH_CRASHING; // Put Away Saber When It Is Not Active //-------------------------------------- #ifndef _JK2MP if (HasWeapon && (Turbo || (pilotPS->weapon==WP_SABER && !pilotPS->SaberActive()))) { if (pVeh->m_pPilot->s.number<MAX_CLIENTS) { CG_ChangeWeapon(WP_NONE); } pVeh->m_pPilot->client->ps.weapon = WP_NONE; G_RemoveWeaponModels(pVeh->m_pPilot); } #endif // Don't Interrupt Attack Anims //------------------------------ #ifdef _JK2MP if (pilotPS->weaponTime>0) { return; } #else if (pilotPS->torsoAnim>=BOTH_VS_ATL_S && pilotPS->torsoAnim<=BOTH_VS_ATF_G) { float bodyCurrent = 0.0f; int bodyEnd = 0; if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) { if (bodyCurrent<=((float)(bodyEnd)-1.5f)) { return; } } } #endif // Compute The Weapon Pose //-------------------------- if (pilotPS->weapon==WP_BLASTER) { WeaponPose = WPOSE_BLASTER; } else if (pilotPS->weapon==WP_SABER) { if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATL_TO_R_S) { pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; } if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATR_TO_L_S) { pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; } WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); } if (Attacking && WeaponPose) {// Attack! iBlend = 100; iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; // Auto Aiming //=============================================== if (!Left && !Right) // Allow player strafe keys to override { #ifndef _JK2MP if (pVeh->m_pPilot->enemy) { vec3_t toEnemy; float toEnemyDistance; vec3_t actorRight; float actorRightDot; VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); toEnemyDistance = VectorNormalize(toEnemy); AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); actorRightDot = DotProduct(toEnemy, actorRight); if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) { Left = (actorRightDot>0.0f); Right = !Left; } else { Right = Left = false; } } else #endif if (pilotPS->weapon==WP_SABER && !Left && !Right) { Left = (WeaponPose==WPOSE_SABERLEFT); Right = !Left; } } if (Left) {// Attack Left switch(WeaponPose) { case WPOSE_BLASTER: Anim = BOTH_VS_ATL_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_S; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_TO_L_S; break; default: assert(0); } } else if (Right) {// Attack Right switch(WeaponPose) { case WPOSE_BLASTER: Anim = BOTH_VS_ATR_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_TO_R_S; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_S; break; default: assert(0); } } else {// Attack Ahead switch(WeaponPose) { case WPOSE_BLASTER: Anim = BOTH_VS_ATF_G; break; default: assert(0); } } } else if (Left && pVeh->m_ucmd.buttons&BUTTON_USE) {// Look To The Left Behind iBlend = 400; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; switch(WeaponPose) { case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; default: Anim = BOTH_VS_LOOKLEFT; } } else if (Right && pVeh->m_ucmd.buttons&BUTTON_USE) {// Look To The Right Behind iBlend = 400; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; switch(WeaponPose) { case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; default: Anim = BOTH_VS_LOOKRIGHT; } } else if (Turbo) {// Kicked In Turbo iBlend = 50; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; Anim = BOTH_VS_TURBO; } else if (Flying) {// Off the ground in a jump iBlend = 800; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; switch(WeaponPose) { case WPOSE_NONE: Anim = BOTH_VS_AIR; break; case WPOSE_BLASTER: Anim = BOTH_VS_AIR_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_AIR_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_AIR_SR; break; default: assert(0); } } else if (Crashing) {// Hit the ground! iBlend = 100; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; switch(WeaponPose) { case WPOSE_NONE: Anim = BOTH_VS_LAND; break; case WPOSE_BLASTER: Anim = BOTH_VS_LAND_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_LAND_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_LAND_SR; break; default: assert(0); } } else {// No Special Moves iBlend = 300; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; if (pVeh->m_vOrientation[ROLL] <= -20) {// Lean Left switch(WeaponPose) { case WPOSE_NONE: Anim = BOTH_VS_LEANL; break; case WPOSE_BLASTER: Anim = BOTH_VS_LEANL_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANL_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANL_SR; break; default: assert(0); } } else if (pVeh->m_vOrientation[ROLL] >= 20) {// Lean Right switch(WeaponPose) { case WPOSE_NONE: Anim = BOTH_VS_LEANR; break; case WPOSE_BLASTER: Anim = BOTH_VS_LEANR_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANR_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANR_SR; break; default: assert(0); } } else {// No Lean switch(WeaponPose) { case WPOSE_NONE: Anim = BOTH_VS_IDLE; break; case WPOSE_BLASTER: Anim = BOTH_VS_IDLE_G; break; case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; default: assert(0); } } }// No Special Moves }// Going backwards? #ifdef _JK2MP iFlags &= ~SETANIM_FLAG_OVERRIDE; if (pVeh->m_pPilot->playerState->torsoAnim == Anim) { pVeh->m_pPilot->playerState->torsoTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); } if (pVeh->m_pPilot->playerState->legsAnim == Anim) { pVeh->m_pPilot->playerState->legsTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); } BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, SETANIM_BOTH, Anim, iFlags|SETANIM_FLAG_HOLD, iBlend); #else NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); #endif }
void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc, int voiceEvent = -1 ) { //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime && mod != MOD_ELECTROCUTE && mod != MOD_MELEE ) {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? return; } int pain_anim = -1; float pain_chance; if ( self->s.weapon == WP_THERMAL && self->client->fireDelay > 0 ) {//don't interrupt thermal throwing anim return; } else if (self->client->ps.powerups[PW_GALAK_SHIELD]) { return; } else if ( self->client->NPC_class == CLASS_GALAKMECH ) { if ( hitLoc == HL_GENERIC1 ) {//hit the antenna! pain_chance = 1.0f; self->s.powerups |= ( 1 << PW_SHOCKED ); self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); } else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) {//shield up return; } else if ( self->health > 200 && damage < 100 ) {//have a *lot* of health pain_chance = 0.05f; } else {//the lower my health and greater the damage, the more likely I am to play a pain anim pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; } } else if ( self->client && self->client->playerTeam == TEAM_PLAYER && other && !other->s.number ) {//ally shot by player always complains pain_chance = 1.1f; } else { if ( other && (other->s.weapon == WP_SABER || mod == MOD_ELECTROCUTE || mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/) ) { if ( self->client->ps.weapon == WP_SABER && other->s.number < MAX_CLIENTS ) {//hmm, shouldn't *always* react to damage from player if I have a saber pain_chance = 1.05f - ((self->NPC->rank)/(float)RANK_CAPTAIN); } else { pain_chance = 1.0f;//always take pain from saber } } else if ( mod == MOD_GAS ) { pain_chance = 1.0f; } else if ( mod == MOD_MELEE ) {//higher in rank (skill) we are, less likely we are to be fazed by a punch pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); } else if ( self->client->NPC_class == CLASS_PROTOCOL ) { pain_chance = 1.0f; } else { pain_chance = NPC_GetPainChance( self, damage ); } if ( self->client->NPC_class == CLASS_DESANN ) { pain_chance *= 0.5f; } } //See if we're going to flinch if ( Q_flrand(0.0f, 1.0f) < pain_chance ) { //Pick and play our animation if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) { G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } else if ( mod == MOD_GAS ) { //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code! if ( TIMER_Done( self, "gasChokeSound" ) ) { TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) ); G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); } } else if ( (self->client->ps.eFlags&EF_FORCE_DRAINED) ) { NPC_SetPainEvent( self ); } else {//not being force-gripped or force-drained if ( G_CheckForStrongAttackMomentum( self ) || PM_SpinningAnim( self->client->ps.legsAnim ) || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) || PM_InKnockDown( &self->client->ps ) || PM_RollingAnim( self->client->ps.legsAnim ) || (PM_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain return; } else {//play an anim if ( self->client->NPC_class == CLASS_GALAKMECH ) {//only has 1 for now //FIXME: never plays this, it seems... pain_anim = BOTH_PAIN1; } else if ( mod == MOD_MELEE ) { pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( self->s.weapon == WP_SABER ) {//temp HACK: these are the only 2 pain anims that look good when holding a saber pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); } else if ( mod != MOD_ELECTROCUTE ) { pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); } if ( pain_anim == -1 ) { pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 ); } self->client->ps.saberAnimLevel = SS_FAST;//next attack must be a quick attack self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in int parts = SETANIM_BOTH; if ( PM_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) { parts = SETANIM_LEGS; } self->NPC->aiFlags &= ~NPCAI_KNEEL; NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } if ( voiceEvent != -1 ) { G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); } else { NPC_SetPainEvent( self ); } } //Setup the timing for it if ( mod == MOD_ELECTROCUTE ) { self->painDebounceTime = level.time + 4000; } self->painDebounceTime = level.time + PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t) pain_anim ); self->client->fireDelay = 0; } }
qboolean PM_AdjustAnglesForSpinningFlip( gentity_t *ent, usercmd_t *ucmd, qboolean anglesOnly ) { vec3_t newAngles; float animLength, spinStart, spinEnd, spinAmt, spinLength; animNumber_t spinAnim; if ( ent->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN ) { spinAnim = BOTH_JUMPFLIPSTABDOWN; spinStart = 300.0f;//700.0f; spinEnd = 1400.0f; spinAmt = 180.0f; } else if ( ent->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 ) { spinAnim = BOTH_JUMPFLIPSLASHDOWN1; spinStart = 300.0f;//700.0f;//1500.0f; spinEnd = 1400.0f;//2300.0f; spinAmt = 180.0f; } else { if ( !anglesOnly ) { if ( !ent->s.number ) { cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_VOF; cg.overrides.thirdPersonVertOffset = 0; } } return qfalse; } animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, spinAnim ); float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); //face me if ( elapsedTime >= spinStart && elapsedTime <= spinEnd ) { spinLength = spinEnd - spinStart; VectorCopy( ent->client->ps.viewangles, newAngles ); newAngles[YAW] = ent->angle + (spinAmt * (elapsedTime-spinStart) / spinLength); if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) {//don't clamp angles when looking through a viewEntity SetClientViewAngle( ent, newAngles ); } ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; if ( anglesOnly ) { return qtrue; } } else if ( anglesOnly ) { return qfalse; } //push me if ( ent->client->ps.legsAnimTimer > 300 )//&& ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) {//haven't landed or reached end of anim yet if ( ent->s.number || !player_locked ) { vec3_t pushDir, pushAngles = {0,ent->angle,0}; AngleVectors( pushAngles, pushDir, NULL, NULL ); if ( DotProduct( ent->client->ps.velocity, pushDir ) < 100 ) { VectorMA( ent->client->ps.velocity, 10, pushDir, ent->client->ps.velocity ); } } } //do a dip in the view if ( !ent->s.number ) { float viewDip = 0; if ( elapsedTime < animLength/2.0f ) {//starting anim viewDip = (elapsedTime/animLength)*-120.0f; } else {//ending anim viewDip = ((animLength-elapsedTime)/animLength)*-120.0f; } cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_VOF; cg.overrides.thirdPersonVertOffset = cg_thirdPersonVertOffset.value+viewDip; //pm->ps->viewheight = standheight + viewDip; } return qtrue; }
static void AnimateVehicle( Vehicle_t *pVeh ) { animNumber_t Anim = BOTH_VT_IDLE; int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; gentity_t * pilot = (gentity_t *)pVeh->m_pPilot; gentity_t * parent = (gentity_t *)pVeh->m_pParentEntity; playerState_t * pilotPS; playerState_t * parentPS; float fSpeedPercToMax; #ifdef _JK2MP pilotPS = (pilot)?(pilot->playerState):(0); parentPS = parent->playerState; #else pilotPS = (pilot)?(&pilot->client->ps):(0); parentPS = &parent->client->ps; #endif // We're dead (boarding is reused here so I don't have to make another variable :-). if ( parent->health <= 0 ) { /* if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! { pVeh->m_iBoarding = -999; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); } */ return; } // If they're bucking, play the animation and leave... if ( parent->client->ps.legsAnim == BOTH_VT_BUCK ) { // Done with animation? Erase the flag. if ( parent->client->ps.legsAnimTimer <= 0 ) { pVeh->m_ulFlags &= ~VEH_BUCKING; } else { return; } } else if ( pVeh->m_ulFlags & VEH_BUCKING ) { iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; Anim = BOTH_VT_BUCK; iBlend = 500; Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_BUCK, iFlags, iBlend ); return; } // Boarding animation. if ( pVeh->m_iBoarding != 0 ) { // We've just started boarding, set the amount of time it will take to finish boarding. if ( pVeh->m_iBoarding < 0 ) { int iAnimLen; // Boarding from left... if ( pVeh->m_iBoarding == -1 ) { Anim = BOTH_VT_MOUNT_L; } else if ( pVeh->m_iBoarding == -2 ) { Anim = BOTH_VT_MOUNT_R; } else if ( pVeh->m_iBoarding == -3 ) { Anim = BOTH_VT_MOUNT_B; } // Set the delay time (which happens to be the time it takes for the animation to complete). // NOTE: Here I made it so the delay is actually 70% (0.7f) of the animation time. #ifdef _JK2MP iAnimLen = BG_AnimLength( parent->localAnimIndex, Anim ) * 0.7f; #else iAnimLen = PM_AnimLength( parent->client->clientInfo.animFileIndex, Anim ) * 0.7f; #endif pVeh->m_iBoarding = level.time + iAnimLen; // Set the animation, which won't be interrupted until it's completed. // TODO: But what if he's killed? Should the animation remain persistant??? iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); if (pilot) { Vehicle_SetAnim(pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); } return; } // Otherwise we're done. else if ( pVeh->m_iBoarding <= level.time ) { pVeh->m_iBoarding = 0; } } // Percentage of maximum speed relative to current speed. //float fSpeed = VectorLength( client->ps.velocity ); fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; // Going in reverse... if ( fSpeedPercToMax < -0.01f ) { Anim = BOTH_VT_WALK_REV; iBlend = 600; } else { bool Turbo = (qboolean)(fSpeedPercToMax>0.0f && level.time<pVeh->m_iTurboTime); bool Walking = (qboolean)(fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); bool Running = (qboolean)(fSpeedPercToMax>0.275f); // Remove Crashing Flag //---------------------- pVeh->m_ulFlags &= ~VEH_CRASHING; if (Turbo) {// Kicked In Turbo iBlend = 50; iFlags = SETANIM_FLAG_OVERRIDE; Anim = BOTH_VT_TURBO; } else {// No Special Moves iBlend = 300; iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; Anim = (Walking)?(BOTH_VT_WALK_FWD ):((Running)?(BOTH_VT_RUN_FWD ):(BOTH_VT_IDLE1)); } } Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); }
//------------------------------ static void Howler_Attack( float enemyDist, qboolean howl ) { int dmg = (NPCInfo->localState==LSTATE_BERZERK)?5:2; if ( !TIMER_Exists( NPC, "attacking" )) { int attackAnim = BOTH_GESTURE1; // Going to do an attack if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps ) && enemyDist <= MIN_DISTANCE ) { attackAnim = BOTH_ATTACK2; } else if ( !Q_irand( 0, 4 ) || howl ) {//howl attack //G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" ); } else if ( enemyDist > MIN_DISTANCE && Q_irand( 0, 1 ) ) {//lunge attack //jump foward vec3_t fwd, yawAng = {0, NPC->client->ps.viewangles[YAW], 0}; AngleVectors( yawAng, fwd, NULL, NULL ); VectorScale( fwd, (enemyDist*3.0f), NPC->client->ps.velocity ); NPC->client->ps.velocity[2] = 200; NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; attackAnim = BOTH_ATTACK1; } else {//tongue attack attackAnim = BOTH_ATTACK2; } NPC_SetAnim( NPC, SETANIM_BOTH, attackAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_RESTART ); if ( NPCInfo->localState == LSTATE_BERZERK ) {//attack again right away TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer ); } else { TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand( 0, 1500 ) );//FIXME: base on skill TIMER_Set( NPC, "standing", -level.time ); TIMER_Set( NPC, "walking", -level.time ); TIMER_Set( NPC, "running", NPC->client->ps.legsAnimTimer + 5000 ); } TIMER_Set( NPC, "attack_dmg", 200 ); // level two damage } // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks switch ( NPC->client->ps.legsAnim ) { case BOTH_ATTACK1: case BOTH_MELEE1: if ( NPC->client->ps.legsAnimTimer > 650//more than 13 frames left && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 800 )//at least 16 frames into anim { Howler_TryDamage( dmg, qfalse, qfalse ); } break; case BOTH_ATTACK2: case BOTH_MELEE2: if ( NPC->client->ps.legsAnimTimer > 350//more than 7 frames left && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 550 )//at least 11 frames into anim { Howler_TryDamage( dmg, qtrue, qfalse ); } break; case BOTH_GESTURE1: { if ( NPC->client->ps.legsAnimTimer > 1800//more than 36 frames left && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 950 )//at least 19 frames into anim { Howler_Howl(); if ( !NPC->count ) { G_PlayEffect( G_EffectIndex( "howler/sonic" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 4750, qtrue ); G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" ); NPC->count = 1; } } } break; default: //anims seem to get reset after a load, so just stop attacking and it will restart as needed. TIMER_Remove( NPC, "attacking" ); break; } // Just using this to remove the attacking flag at the right time TIMER_Done2( NPC, "attacking", qtrue ); }
void NPC_ChoosePainAnimation( gentity_t *self, int damage ) { //If we've already taken pain, then don't take it again if ( level.time < self->painDebounceTime ) return; int pain_anim; float pain_chance = NPC_GetPainChance( self, damage ); //See what we want to do switch( (int) self->client->playerTeam ) { //Crewmembers shouldn't base their pain on skill level case TEAM_STARFLEET: //Don't always take pain pain_chance = 0.25f; //25% break; case TEAM_BOTS: //Never take pain if ( ( Q_stricmp( self->NPC_type, "warriorbot" ) == 0 ) || ( Q_stricmp( self->NPC_type, "warriorbot_boss" ) == 0 ) ) { //Have to hit them hard to make them flinch if ( damage < 50 ) return; //Take it less often pain_chance *= 0.5f; } break; case TEAM_FORGE: //Never take pain if ( Q_stricmp( self->NPC_type, "Vohrsoth" ) == 0 ) return; break; //Hirogen Alpha does special shield maintenance case TEAM_HIROGEN: if ( Q_stricmp( self->NPC_type, "hirogenalpha" ) == 0 ) { if ( Q_irand( 0, 1 ) ) { //Set our pain animation self->painDebounceTime = level.time + 1000; NPC_SetAnim(self,SETANIM_BOTH,BOTH_PAIN1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } else { //Enraged self->painDebounceTime = level.time + 1000; NPC_SetAnim(self,SETANIM_BOTH,BOTH_POWERUP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); } //Turn the shield back on immediately, only allow one hit per shield drop //FIXME: Is this reliable? if ( self->client->ps.powerups[ PW_HIROGEN_SHIELD ] != -1 && !self->NPC->ignorePain ) { self->s.powerups |= ( 1 << PW_HIROGEN_SHIELD ); self->client->ps.powerups[ PW_HIROGEN_SHIELD ] = level.time + 10000; NPC_SetPainEvent( self ); } return; } break; //All other NPC pain reactions default: break; } //See if we're going to flinch if ( random() < pain_chance ) { //Pick and play our animation pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN3 ); //initialize to good data NPC_SetAnim( self, SETANIM_BOTH, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); //Setup the timing for it self->painDebounceTime = level.time + PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) pain_anim ); self->client->fireDelay = 0; NPC_SetPainEvent( self ); } }