void Q3_TaskIDSet( sharedEntity_t *ent, taskID_t taskType, int taskID ) { if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) { return; } //Might be stomping an old task, so complete and clear previous task if there was one Q3_TaskIDComplete( ent, taskType ); ent->taskID[taskType] = taskID; }
void G_CheckTasksCompleted (gentity_t *ent) { if ( Q3_TaskIDPending( ent, TID_CHAN_VOICE ) ) { if ( !gi.VoiceVolume[ent->s.number] ) {//not playing a voice sound //return task_complete Q3_TaskIDComplete( ent, TID_CHAN_VOICE ); } } if ( Q3_TaskIDPending( ent, TID_LOCATION ) ) { char *currentLoc = G_GetLocationForEnt( ent ); if ( currentLoc && currentLoc[0] && Q_stricmp( ent->message, currentLoc ) == 0 ) {//we're in the desired location Q3_TaskIDComplete( ent, TID_LOCATION ); } //FIXME: else see if were in other trigger_locations? } }
void NPC_ReachedGoal( void ) { // Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "UpdateGoal: reached goal entity\n" ); NPC_ClearGoal(); NPCInfo->goalTime = level.time; //MCG - Begin NPCInfo->aiFlags &= ~NPCAI_MOVING; ucmd.forwardmove = 0; //Return that the goal was reached Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); //MCG - End }
//Simply turn until facing desired angles void NPC_BSFace (void) { //FIXME: once you stop sending turning info, they reset to whatever their delta_angles was last???? //Once this is over, it snaps back to what it was facing before- WHY??? if( NPC_UpdateAngles ( qtrue, qtrue ) ) { Q3_TaskIDComplete( NPC, TID_BSTATE ); NPCInfo->desiredYaw = client->ps.viewangles[YAW]; NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; NPCInfo->aimTime = 0;//ok to turn normally now } }
void NPC_BSJump (void) { vec3_t dir, angles, p1, p2, apex; float time, height, forward, z, xy, dist, yawError, apexHeight; if( !NPCInfo->goalEntity ) {//Should have task completed the navgoal return; } if ( NPCInfo->jumpState != JS_JUMPING && NPCInfo->jumpState != JS_LANDING ) { //Face navgoal VectorSubtract(NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir); vectoangles(dir, angles); NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); } NPC_UpdateAngles ( qtrue, qtrue ); yawError = AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ); //We don't really care about pitch here switch ( NPCInfo->jumpState ) { case JS_FACING: if ( yawError < MIN_ANGLE_ERROR ) {//Facing it, Start crouching NPC_SetAnim(NPC, SETANIM_LEGS, BOTH_CROUCH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); NPCInfo->jumpState = JS_CROUCHING; } break; case JS_CROUCHING: if ( NPC->client->ps.legsAnimTimer > 0 ) {//Still playing crouching anim return; } //Create a parabola if ( NPC->currentOrigin[2] > NPCInfo->goalEntity->currentOrigin[2] ) { VectorCopy( NPC->currentOrigin, p1 ); VectorCopy( NPCInfo->goalEntity->currentOrigin, p2 ); } else if ( NPC->currentOrigin[2] < NPCInfo->goalEntity->currentOrigin[2] ) { VectorCopy( NPCInfo->goalEntity->currentOrigin, p1 ); VectorCopy( NPC->currentOrigin, p2 ); } else { VectorCopy( NPC->currentOrigin, p1 ); VectorCopy( NPCInfo->goalEntity->currentOrigin, p2 ); } //z = xy*xy VectorSubtract( p2, p1, dir ); dir[2] = 0; //Get xy and z diffs xy = VectorNormalize( dir ); z = p1[2] - p2[2]; apexHeight = APEX_HEIGHT/2; /* //Determine most desirable apex height apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128); if ( apexHeight < APEX_HEIGHT * 0.5 ) { apexHeight = APEX_HEIGHT*0.5; } else if ( apexHeight > APEX_HEIGHT * 2 ) { apexHeight = APEX_HEIGHT*2; } */ //FIXME: length of xy will change curve of parabola, need to account for this //somewhere... PARA_WIDTH z = (sqrt(apexHeight + z) - sqrt(apexHeight)); assert(z >= 0); // gi.Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f); xy -= z; xy *= 0.5; assert(xy > 0); VectorMA( p1, xy, dir, apex ); apex[2] += apexHeight; VectorCopy(apex, NPC->pos1); //Now we have the apex, aim for it height = apex[2] - NPC->currentOrigin[2]; time = sqrt( height / ( .5 * NPC->client->ps.gravity ) ); if ( !time ) { // gi.Printf("ERROR no time in jump\n"); return; } // set s.origin2 to the push velocity VectorSubtract ( apex, NPC->currentOrigin, NPC->client->ps.velocity ); NPC->client->ps.velocity[2] = 0; dist = VectorNormalize( NPC->client->ps.velocity ); forward = dist / time; VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity ); NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity; // gi.Printf( "%s jumping %s, gravity at %4.0f percent\n", NPC->targetname, vtos(NPC->client->ps.velocity), NPC->client->ps.gravity/8.0f ); NPC->flags |= FL_NO_KNOCKBACK; NPCInfo->jumpState = JS_JUMPING; //FIXME: jumpsound? break; case JS_JUMPING: if ( showBBoxes ) { VectorAdd(NPC->mins, NPC->pos1, p1); VectorAdd(NPC->maxs, NPC->pos1, p2); CG_Cube( p1, p2, NPCDEBUG_BLUE, 0.5 ); } if ( NPC->s.groundEntityNum != ENTITYNUM_NONE) {//Landed, start landing anim //FIXME: if the VectorClear(NPC->client->ps.velocity); NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_LAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); NPCInfo->jumpState = JS_LANDING; //FIXME: landsound? } else if ( NPC->client->ps.legsAnimTimer > 0 ) {//Still playing jumping anim //FIXME: apply jump velocity here, a couple frames after start, not right away return; } else {//still in air, but done with jump anim, play inair anim NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_OVERRIDE); } break; case JS_LANDING: if ( NPC->client->ps.legsAnimTimer > 0 ) {//Still playing landing anim return; } else { NPCInfo->jumpState = JS_WAITING; //task complete no matter what... NPC_ClearGoal(); NPCInfo->goalTime = level.time; NPCInfo->aiFlags &= ~NPCAI_MOVING; ucmd.forwardmove = 0; NPC->flags &= ~FL_NO_KNOCKBACK; //Return that the goal was reached Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); //Or should we keep jumping until reached goal? /* NPCInfo->goalEntity = UpdateGoal(); if ( !NPCInfo->goalEntity ) { NPC->flags &= ~FL_NO_KNOCKBACK; Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); } */ } break; case JS_WAITING: default: NPCInfo->jumpState = JS_FACING; break; } }
/* void NPC_BSAdvanceFight (void) Advance towards your captureGoal and shoot anyone you can along the way. */ void NPC_BSAdvanceFight (void) {//FIXME: IMPLEMENT //Head to Goal if I can //Make sure we're still headed where we want to capture if ( NPCInfo->captureGoal ) {//FIXME: if no captureGoal, what do we do? //VectorCopy( NPCInfo->captureGoal->currentOrigin, NPCInfo->tempGoal->currentOrigin ); //NPCInfo->goalEntity = NPCInfo->tempGoal; NPC_SetMoveGoal( NPC, NPCInfo->captureGoal->currentOrigin, 16, qtrue ); // NAV_ClearLastRoute(NPC); NPCInfo->goalTime = level.time + 100000; } // NPC_BSRun(); NPC_CheckEnemy(qtrue, qfalse); //FIXME: Need melee code if( NPC->enemy ) {//See if we can shoot him vec3_t delta, forward; vec3_t angleToEnemy; vec3_t hitspot, muzzle, diff, enemy_org, enemy_head; float distanceToEnemy; qboolean attack_ok = qfalse; qboolean dead_on = qfalse; float attack_scale = 1.0; float aim_off; float max_aim_off = 64; //Yaw to enemy VectorMA(NPC->enemy->absmin, 0.5, NPC->enemy->maxs, enemy_org); CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); VectorSubtract (enemy_org, muzzle, delta); vectoangles ( delta, angleToEnemy ); distanceToEnemy = VectorNormalize(delta); if(!NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue)) { attack_ok = qtrue; } if(attack_ok) { NPC_UpdateShootAngles(angleToEnemy, qfalse, qtrue); NPCInfo->enemyLastVisibility = enemyVisibility; enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV);//CHECK_360|//CHECK_PVS| if(enemyVisibility == VIS_FOV) {//He's in our FOV attack_ok = qtrue; CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); if(attack_ok) { trace_t tr; gentity_t *traceEnt; //are we gonna hit him if we shoot at his center? gi.trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 ); traceEnt = &g_entities[tr.entityNum]; if( traceEnt != NPC->enemy && (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) {//no, so shoot for the head attack_scale *= 0.75; gi.trace ( &tr, muzzle, NULL, NULL, enemy_head, NPC->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 ); traceEnt = &g_entities[tr.entityNum]; } VectorCopy( tr.endpos, hitspot ); if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) { dead_on = qtrue; } else { attack_scale *= 0.5; if(NPC->client->playerTeam) { if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) { if(NPC->client->playerTeam == traceEnt->client->playerTeam) {//Don't shoot our own team attack_ok = qfalse; } } } } } if( attack_ok ) { //ok, now adjust pitch aim VectorSubtract (hitspot, muzzle, delta); vectoangles ( delta, angleToEnemy ); NPC->NPC->desiredPitch = angleToEnemy[PITCH]; NPC_UpdateShootAngles(angleToEnemy, qtrue, qfalse); if( !dead_on ) {//We're not going to hit him directly, try a suppressing fire //see if where we're going to shoot is too far from his origin AngleVectors (NPCInfo->shootAngles, forward, NULL, NULL); VectorMA ( muzzle, distanceToEnemy, forward, hitspot); VectorSubtract(hitspot, enemy_org, diff); aim_off = VectorLength(diff); if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? { attack_scale *= 0.75; //see if where we're going to shoot is too far from his head VectorSubtract(hitspot, enemy_head, diff); aim_off = VectorLength(diff); if(aim_off > random() * max_aim_off) { attack_ok = qfalse; } } attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; } } } } if( attack_ok ) { if( NPC_CheckAttack( attack_scale )) {//check aggression to decide if we should shoot enemyVisibility = VIS_SHOOT; WeaponThink(qtrue); } else attack_ok = qfalse; } //Don't do this- only for when stationary and trying to shoot an enemy // else // NPC->cantHitEnemyCounter++; } else {//FIXME: NPC_UpdateShootAngles(NPC->client->ps.viewangles, qtrue, qtrue); } if(!ucmd.forwardmove && !ucmd.rightmove) {//We reached our captureGoal if(NPC->taskManager) { Q3_TaskIDComplete( NPC, TID_BSTATE ); } } }
qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) { #if 1 float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; qboolean exact = qtrue; // if angle changes are locked; just keep the current angles // aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) || NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE) ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { // we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; if(doPitch) { targetPitch = NPCInfo->desiredPitch; NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; } if(doYaw) { targetYaw = NPCInfo->desiredYaw; NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } } if ( NPC->s.weapon == WP_EMPLACED_GUN ) { // FIXME: this seems to do nothing, actually... yawSpeed = 20; } else { yawSpeed = NPCInfo->stats.yawSpeed; } if ( NPC->s.weapon == WP_SABER && NPC->client->ps.forcePowersActive&(1<<FP_SPEED) ) { yawSpeed *= 1.0f/g_timescale->value; } if( doYaw ) { // decay yaw error error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if( fabs(error) > MIN_ANGLE_ERROR ) { if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; } //FIXME: have a pitchSpeed? if( doPitch ) { // decay pitch error error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( fabs(error) > MIN_ANGLE_ERROR ) { if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0f / 1000.0f;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; if ( exact && Q3_TaskIDPending( NPC, TID_ANGLE_FACE ) ) { Q3_TaskIDComplete( NPC, TID_ANGLE_FACE ); } return exact; #else float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; //float runningMod = NPCInfo->currentSpeed/100.0f; qboolean exact = qtrue; qboolean doSound = qfalse; // if angle changes are locked; just keep the current angles if ( level.time < NPCInfo->aimTime ) { if(doPitch) targetPitch = NPCInfo->lockedDesiredPitch; if(doYaw) targetYaw = NPCInfo->lockedDesiredYaw; } else { if(doPitch) targetPitch = NPCInfo->desiredPitch; if(doYaw) targetYaw = NPCInfo->desiredYaw; // NPCInfo->aimTime = level.time + 250; if(doPitch) NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; if(doYaw) NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } yawSpeed = NPCInfo->stats.yawSpeed; if(doYaw) { // decay yaw error error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if( fabs(error) > MIN_ANGLE_ERROR ) { /* if(NPC->client->playerTeam == TEAM_BORG&& NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) {//HACK - borg turn more jittery if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec //Snap to if(fabs(error) > 10) { if(random() > 0.6) { doSound = qtrue; } } if ( error < 0.0)//-10.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else if ( error > 0.0)//10.0 ) { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } else*/ if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; } //FIXME: have a pitchSpeed? if(doPitch) { // decay pitch error error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( fabs(error) > MIN_ANGLE_ERROR ) { /* if(NPC->client->playerTeam == TEAM_BORG&& NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) {//HACK - borg turn more jittery if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec //Snap to if(fabs(error) > 10) { if(random() > 0.6) { doSound = qtrue; } } if ( error < 0.0)//-10.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else if ( error > 0.0)//10.0 ) { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } else*/ if ( error ) { exact = qfalse; decay = 60.0 + yawSpeed * 3; decay *= 50.0 / 1000.0;//msec if ( error < 0.0 ) { error += decay; if ( error > 0.0 ) { error = 0.0; } } else { error -= decay; if ( error < 0.0 ) { error = 0.0; } } } } ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; /* if(doSound) { G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8)))); } */ return exact; #endif }
void G_Roff( gentity_t *ent ) { if ( !ent->next_roff_time ) { return; } if ( ent->next_roff_time > level.time ) {// either I don't think or it's just not time to have me think yet return; } const int roff_id = G_LoadRoff( ent->roff ); if ( !roff_id ) { // Couldn't cache this rof return; } // The ID is one higher than the array index const roff_list_t * roff = &roffs[ roff_id - 1 ]; vec3_t org, ang; if ( roff->type == 2 ) { move_rotate2_t *data = &((move_rotate2_t *)roff->data)[ ent->roff_ctr ]; VectorCopy( data->origin_delta, org ); VectorCopy( data->rotate_delta, ang ); if (data->mStartNote != -1 || data->mNumNotes) { G_RoffNotetrackCallback(ent, roffs[roff_id - 1].mNoteTrackIndexes[data->mStartNote]); } } else { move_rotate_t *data = &((move_rotate_t *)roff->data)[ ent->roff_ctr ]; VectorCopy( data->origin_delta, org ); VectorCopy( data->rotate_delta, ang ); } #ifdef _DEBUG if ( g_developer->integer ) { Com_Printf( S_COLOR_GREEN"ROFF dat: num: %d o:<%.2f %.2f %.2f> a:<%.2f %.2f %.2f>\n", ent->roff_ctr, org[0], org[1], org[2], ang[0], ang[1], ang[2] ); } #endif if ( ent->client ) { // Set up the angle interpolation //------------------------------------- VectorAdd( ent->s.apos.trBase, ang, ent->s.apos.trBase ); ent->s.apos.trTime = level.time; ent->s.apos.trType = TR_INTERPOLATE; // Store what the next apos->trBase should be VectorCopy( ent->s.apos.trBase, ent->client->ps.viewangles ); VectorCopy( ent->s.apos.trBase, ent->currentAngles ); VectorCopy( ent->s.apos.trBase, ent->s.angles ); if ( ent->NPC ) { //ent->NPC->desiredPitch = ent->s.apos.trBase[PITCH]; ent->NPC->desiredYaw = ent->s.apos.trBase[YAW]; } // Set up the origin interpolation //------------------------------------- VectorAdd( ent->s.pos.trBase, org, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; ent->s.pos.trType = TR_INTERPOLATE; // Store what the next pos->trBase should be VectorCopy( ent->s.pos.trBase, ent->client->ps.origin ); VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); //VectorCopy( ent->s.pos.trBase, ent->s.origin ); } else { // Set up the angle interpolation //------------------------------------- VectorScale( ang, roff->mLerp, ent->s.apos.trDelta ); VectorCopy( ent->pos2, ent->s.apos.trBase ); ent->s.apos.trTime = level.time; ent->s.apos.trType = TR_LINEAR; // Store what the next apos->trBase should be VectorAdd( ent->pos2, ang, ent->pos2 ); // Set up the origin interpolation //------------------------------------- VectorScale( org, roff->mLerp, ent->s.pos.trDelta ); VectorCopy( ent->pos1, ent->s.pos.trBase ); ent->s.pos.trTime = level.time; ent->s.pos.trType = TR_LINEAR; // Store what the next apos->trBase should be VectorAdd( ent->pos1, org, ent->pos1 ); //make it true linear... FIXME: sticks around after ROFF is done, but do we really care? ent->alt_fire = qtrue; if ( ent->e_ThinkFunc == thinkF_TieFighterThink || ent->e_ThinkFunc == thinkF_TieBomberThink || ( !ent->e_ThinkFunc && ent->s.eType != ET_MISSILE && ent->s.eType != ET_ITEM && ent->s.eType != ET_MOVER ) ) {//will never set currentAngles & currentOrigin itself ( why do we limit which one's get set?, just set all the time? ) EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles ); EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin ); } } // Link just in case. gi.linkentity( ent ); // See if the ROFF playback is done //------------------------------------- if ( ++ent->roff_ctr >= roff->frames ) { // We are done, so let me think no more, then tell the task that we're done. ent->next_roff_time = 0; // Stop any rotation or movement. VectorClear( ent->s.pos.trDelta ); VectorClear( ent->s.apos.trDelta ); Q3_TaskIDComplete( ent, TID_MOVE_NAV ); return; } ent->next_roff_time = level.time + roff->mFrameTime; }
void G_Animate ( gentity_t *self ) { if ( self->s.eFlags & EF_SHADER_ANIM ) { return; } if ( self->s.frame == self->endFrame ) { if ( self->svFlags & SVF_ANIMATING ) { // ghoul2 requires some extra checks to see if the animation is done since it doesn't set the current frame directly if ( self->ghoul2.size() ) { float frame, junk2; int junk; // I guess query ghoul2 to find out what the current frame is and see if we are done. gi.G2API_GetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, (cg.time?cg.time:level.time), &frame, &junk, &junk, &junk, &junk2, NULL ); // It NEVER seems to get to what you'd think the last frame would be, so I'm doing this to try and catch when the animation has stopped if ( frame + 1 >= self->endFrame ) { self->svFlags &= ~SVF_ANIMATING; Q3_TaskIDComplete( self, TID_ANIM_BOTH ); } } else // not ghoul2 { if ( self->loopAnim ) { self->s.frame = self->startFrame; } else { self->svFlags &= ~SVF_ANIMATING; } //Finished sequence - FIXME: only do this once even on looping anims? Q3_TaskIDComplete( self, TID_ANIM_BOTH ); } } return; } self->svFlags |= SVF_ANIMATING; // With ghoul2, we'll just set the desired start and end frame and let it do it's thing. if ( self->ghoul2.size()) { self->s.frame = self->endFrame; gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, self->startFrame, self->endFrame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time ); return; } if ( self->startFrame < self->endFrame ) { if ( self->s.frame < self->startFrame || self->s.frame > self->endFrame ) { self->s.frame = self->startFrame; } else { self->s.frame++; } } else if ( self->startFrame > self->endFrame ) { if ( self->s.frame > self->startFrame || self->s.frame < self->endFrame ) { self->s.frame = self->startFrame; } else { self->s.frame--; } } else { self->s.frame = self->endFrame; } }
void NPC_BSPointShoot (qboolean shoot) {//FIXME: doesn't check for clear shot... vec3_t muzzle, dir, angles, org; if ( !NPC->enemy || !NPC->enemy->inuse || (NPC->enemy->NPC && NPC->enemy->health <= 0) ) {//FIXME: should still keep shooting for a second or two after they actually die... Q3_TaskIDComplete( NPC, TID_BSTATE ); goto finished; return; } CalcEntitySpot(NPC, SPOT_WEAPON, muzzle); CalcEntitySpot(NPC->enemy, SPOT_HEAD, org);//Was spot_org //Head is a little high, so let's aim for the chest: if ( NPC->enemy->client ) { org[2] -= 12;//NOTE: is this enough? } VectorSubtract(org, muzzle, dir); vectoangles(dir, angles); switch( NPC->client->ps.weapon ) { case WP_NONE: // case WP_TRICORDER: case WP_MELEE: case WP_SABER: //don't do any pitch change if not holding a firing weapon break; default: NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); break; } NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); if ( NPC_UpdateAngles ( qtrue, qtrue ) ) {//FIXME: if angles clamped, this may never work! //NPCInfo->shotTime = NPC->attackDebounceTime = 0; if ( shoot ) {//FIXME: needs to hold this down if using a weapon that requires it, like phaser... ucmd.buttons |= BUTTON_ATTACK; } if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) ) {//If locked_enemy is on, dont complete until it is destroyed... Q3_TaskIDComplete( NPC, TID_BSTATE ); goto finished; } } else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) ) {//shooting them till their dead, not aiming right at them yet... /* qboolean movingTarget = qfalse; if ( NPC->enemy->client ) { if ( VectorLengthSquared( NPC->enemy->client->ps.velocity ) ) { movingTarget = qtrue; } } else if ( VectorLengthSquared( NPC->enemy->s.pos.trDelta ) ) { movingTarget = qtrue; } if (movingTarget ) */ { float dist = VectorLength( dir ); float yawMiss, yawMissAllow = NPC->enemy->maxs[0]; float pitchMiss, pitchMissAllow = (NPC->enemy->maxs[2] - NPC->enemy->mins[2])/2; if ( yawMissAllow < 8.0f ) { yawMissAllow = 8.0f; } if ( pitchMissAllow < 8.0f ) { pitchMissAllow = 8.0f; } yawMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ))) * dist; pitchMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[PITCH], NPCInfo->desiredPitch))) * dist; if ( yawMissAllow >= yawMiss && pitchMissAllow > pitchMiss ) { ucmd.buttons |= BUTTON_ATTACK; } } } return; finished: NPCInfo->desiredYaw = client->ps.viewangles[YAW]; NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; NPCInfo->aimTime = 0;//ok to turn normally now }