/* ------------------------- R2D2_TurnAnims ------------------------- */ void R2D2_TurnAnims ( void ) { float turndelta; int anim; turndelta = AngleDelta(NPC->r.currentAngles[YAW], NPCInfo->desiredYaw); if ((fabs(turndelta) > 20) && ((NPC->client->NPC_class == CLASS_R2D2) || (NPC->client->NPC_class == CLASS_R5D2))) { anim = NPC->client->ps.legsAnim; if (turndelta<0) { if (anim != BOTH_TURN_LEFT1) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } else { if (anim != BOTH_TURN_RIGHT1) { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } } } else { NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); } }
qboolean InFOV( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ) { vec3_t deltaVector, angles, deltaAngles; VectorSubtract ( spot, from, deltaVector ); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } return qfalse; }
qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ) { vec3_t eyes; vec3_t spot; vec3_t deltaVector; vec3_t angles, fromAngles; vec3_t deltaAngles; if( from->client ) { if( !VectorCompare( from->client->renderInfo.eyeAngles, vec3_origin ) ) {//Actual facing of tag_head! //NOTE: Stasis aliens may have a problem with this? VectorCopy( from->client->renderInfo.eyeAngles, fromAngles ); } else { VectorCopy( from->client->ps.viewangles, fromAngles ); } } else { VectorCopy(from->s.angles, fromAngles); } CalcEntitySpot( from, SPOT_HEAD_LEAN, eyes ); CalcEntitySpot( ent, SPOT_ORIGIN, spot ); VectorSubtract ( spot, eyes, deltaVector); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } CalcEntitySpot( ent, SPOT_HEAD, spot ); VectorSubtract ( spot, eyes, deltaVector); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } CalcEntitySpot( ent, SPOT_LEGS, spot ); VectorSubtract ( spot, eyes, deltaVector); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } return qfalse; }
float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV ) { vec3_t deltaVector, angles; float delta; VectorSubtract ( spot, from, deltaVector ); vectoangles ( deltaVector, angles ); delta = fabs( AngleDelta ( facing[PITCH], angles[PITCH] ) ); if ( delta > vFOV ) return 0.0f; return ( ( vFOV - delta ) / vFOV ); }
float NPC_GetHFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float hFOV ) { vec3_t deltaVector, angles; float delta; VectorSubtract ( spot, from, deltaVector ); vectoangles ( deltaVector, angles ); delta = fabs( AngleDelta ( facing[YAW], angles[YAW] ) ); if ( delta > hFOV ) return 0.0f; return ( ( hFOV - delta ) / hFOV ); }
void CGCam_Roll( float dest, float duration ) { if ( !duration ) { CGCam_SetRoll( dest ); return; } //FIXME/NOTE: this will override current panning!!! client_camera.info_state |= CAMERA_PANNING; VectorCopy( client_camera.angles, client_camera.angles2 ); client_camera.angles2[2] = AngleDelta( dest, client_camera.angles[2] ); client_camera.pan_duration = duration; client_camera.pan_time = cg.time; }
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_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) {//FIXME: shoot angles either not set right or not used! float error; float decay; float targetPitch = 0; float targetYaw = 0; if(doPitch) targetPitch = angles[PITCH]; if(doYaw) targetYaw = angles[YAW]; if(doYaw) { // decay yaw error error = AngleDelta ( NPCInfo->shootAngles[YAW], targetYaw ); if ( error ) { decay = 60.0 + 80.0 * NPCInfo->stats.aim; decay *= 100.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; } } } NPCInfo->shootAngles[YAW] = targetYaw + error; } if(doPitch) { // decay pitch error error = AngleDelta ( NPCInfo->shootAngles[PITCH], targetPitch ); if ( error ) { decay = 60.0 + 80.0 * NPCInfo->stats.aim; decay *= 100.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; } } } NPCInfo->shootAngles[PITCH] = targetPitch + error; } }
/* qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) Includes aim when determining angles - so they don't always hit... */ qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) { #if 0 float diff; float error; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; if ( level.time < NPCInfo->aimTime ) { if( doPitch ) targetPitch = NPCInfo->lockedDesiredPitch; if( doYaw ) targetYaw = NPCInfo->lockedDesiredYaw; } else { if( doPitch ) { targetPitch = NPCInfo->desiredPitch; NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; } if( doYaw ) { targetYaw = NPCInfo->desiredYaw; NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; } } if( doYaw ) { // add yaw error based on NPCInfo->aim value error = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); if(Q_irand(0, 1)) error *= -1; diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff ) exact = qfalse; ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if( doPitch ) { // add pitch error based on NPCInfo->aim value error = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff ) exact = qfalse; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; #else float error, diff; float decay; float targetPitch = 0; float targetYaw = 0; qboolean exact = qtrue; // 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; } if ( NPCInfo->aimErrorDebounceTime < level.time ) { if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); } if ( Q_irand(0, 1 ) ) { NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); } NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); } if(doYaw) { // decay yaw diff diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } // add yaw error based on NPCInfo->aim value error = NPCInfo->lastAimErrorYaw; /* if(Q_irand(0, 1)) { error *= -1; } */ ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; } if(doPitch) { // decay pitch diff diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); if ( diff) { exact = qfalse; decay = 60.0 + 80.0; decay *= 50.0f / 1000.0f;//msec if ( diff < 0.0 ) { diff += decay; if ( diff > 0.0 ) { diff = 0.0; } } else { diff -= decay; if ( diff < 0.0 ) { diff = 0.0; } } } error = NPCInfo->lastAimErrorPitch; ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; } ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; return exact; #endif }
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 }
//Entity to entity qboolean InFOVFromPlayerView ( gentity_t *ent, int hFOV, int vFOV ) { vec3_t eyes; vec3_t spot; vec3_t deltaVector; vec3_t angles, fromAngles; vec3_t deltaAngles; if ( !player || !player->client ) { return qfalse; } if ( cg.time ) { VectorCopy( cg.refdefViewAngles, fromAngles ); } else { VectorCopy( player->client->ps.viewangles, fromAngles ); } if( cg.time ) { VectorCopy( cg.refdef.vieworg, eyes ); } else { CalcEntitySpot( player, SPOT_HEAD_LEAN, eyes ); } CalcEntitySpot( ent, SPOT_ORIGIN, spot ); VectorSubtract ( spot, eyes, deltaVector); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } CalcEntitySpot( ent, SPOT_HEAD, spot ); VectorSubtract ( spot, eyes, deltaVector); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } CalcEntitySpot( ent, SPOT_LEGS, spot ); VectorSubtract ( spot, eyes, deltaVector); vectoangles ( deltaVector, angles ); deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) { return qtrue; } return qfalse; }
void NPC_BSPointShoot (qboolean shoot) {//FIXME: doesn't check for clear shot... vec3_t muzzle, dir, angles, org; //spot_t spot_enemy = SPOT_CHEST; 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... trap_ICARUS_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_STUN_BATON: 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; WeaponThink( qtrue ); } //if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) ) if (1) {//If locked_enemy is on, dont complete until it is destroyed... trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); goto finished; } } //else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) ) if (0) {//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->r.maxs[0]; float pitchMiss, pitchMissAllow = (NPC->enemy->r.maxs[2] - NPC->enemy->r.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 }
/* qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) Added: option to do just pitch or just yaw Does not include "aim" in it's calculations FIXME: stop compressing angles into shorts!!!! */ 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.fd.forcePowersActive&(1<<FP_SPEED) ) { char buf[128]; float tFVal = 0; trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf)); tFVal = atof(buf); yawSpeed *= 1.0f/tFVal; } 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 && trap_ICARUS_TaskIDPending( NPC, TID_ANGLE_FACE ) ) { trap_ICARUS_TaskIDComplete( NPC, TID_ANGLE_FACE ); } return exact; #else float error; float decay; float targetPitch = 0; float targetYaw = 0; float yawSpeed; 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; 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 ( 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 ( 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]; return exact; #endif }
void ClientThink_real( gentity_t *ent, usercmd_t *ucmd ) { gclient_t *client; pmove_t pm; vec3_t oldOrigin; int oldEventSequence; int msec; //Don't let the player do anything if in a camera if ( ent->s.number == 0 ) { extern cvar_t *g_skippingcin; if ( in_camera ) { // watch the code here, you MUST "return" within this IF(), *unless* you're stopping the cinematic skip. // if ( ClientCinematicThink(ent->client) ) { if (g_skippingcin->integer) // already doing cinematic skip? { // yes... so stop skipping... gi.cvar_set("skippingCinematic", "0"); gi.cvar_set("timescale", "1"); } else { // no... so start skipping... gi.cvar_set("skippingCinematic", "1"); gi.cvar_set("timescale", "100"); return; } } else { return; } } else { if ( g_skippingcin->integer ) {//We're skipping the cinematic and it's over now gi.cvar_set("timescale", "1"); gi.cvar_set("skippingCinematic", "0"); } if ( ent->client->ps.pm_type == PM_DEAD && cg.missionStatusDeadTime < level.time ) {//mission status screen is up because player is dead, stop all scripts if (Q_stricmpn(level.mapname,"_holo",5)) { stop_icarus = qtrue; } } } // Don't allow the player to adjust the pitch when they are in third person overhead cam. extern vmCvar_t cg_thirdPerson; if ( cg_thirdPerson.integer == 2 ) { ucmd->angles[PITCH] = 0; } if ( player_locked && ent->client->ps.pm_type < PM_DEAD ) {//lock out player control unless dead VectorClear(ucmd->angles) ; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->buttons = 0; ucmd->upmove = 0; } } else { G_NPCMunroMatchPlayerWeapon( ent ); } client = ent->client; // mark the time, so the connection sprite can be removed client->lastCmdTime = level.time; client->pers.lastCommand = *ucmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; if ( msec < 1 ) { msec = 1; } if ( msec > 200 ) { msec = 200; } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) return; if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } //FIXME: if global gravity changes this should update everyone's personal gravity... if ( !(ent->svFlags & SVF_CUSTOM_GRAVITY) ) { client->ps.gravity = g_gravity->value; } // set speed if ( ent->NPC != NULL ) {//we don't actually scale the ucmd, we use actual speeds if ( ent->NPC->combatMove == qfalse ) { if ( !(ucmd->buttons & BUTTON_USE) ) {//Not leaning qboolean Flying = (ucmd->upmove && ent->NPC->stats.moveType == MT_FLYSWIM); qboolean Climbing = (ucmd->upmove && ent->watertype&CONTENTS_LADDER ); client->ps.friction = 6; if ( ucmd->forwardmove || ucmd->rightmove || Flying ) { if ( ent->NPC->behaviorState != BS_FORMATION ) {//In - Formation NPCs set thier desiredSpeed themselves if ( ucmd->buttons & BUTTON_WALKING ) { ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed; } else//running { ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed; } if ( ent->NPC->currentSpeed >= 80 ) {//At higher speeds, need to slow down close to stuff //Slow down as you approach your goal if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 { if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED ) { float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST; ent->NPC->desiredSpeed = ceil(slowdownSpeed); if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED ) {//don't slow down too much ent->NPC->desiredSpeed = MIN_NPC_SPEED; } } } } } } else if ( Climbing ) { ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed; } else {//We want to stop ent->NPC->desiredSpeed = 0; } NPC_Accelerate( ent, (ent->NPC->behaviorState==BS_FORMATION), (ent->NPC->behaviorState==BS_FORMATION) ); if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) {//No-one walks this slow client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop ucmd->forwardmove = 0; ucmd->rightmove = 0; } else { if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed ) {//Play the walkanim ucmd->buttons |= BUTTON_WALKING; } else { ucmd->buttons &= ~BUTTON_WALKING; } if ( ent->NPC->currentSpeed > 0 ) {//We should be moving if ( Climbing || Flying ) { if ( !ucmd->upmove ) {//We need to force them to take a couple more steps until stopped ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove; } } else if ( !ucmd->forwardmove && !ucmd->rightmove ) {//We need to force them to take a couple more steps until stopped ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove; ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove; } } client->ps.speed = ent->NPC->currentSpeed; //Slow down on turns - don't orbit!!! float turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->desiredYaw ) ))/180; if ( turndelta < 0.75f ) { client->ps.speed = 0; } else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 ) {//Turn is greater than 45 degrees or closer than 100 to goal client->ps.speed = floor(((float)(client->ps.speed))*turndelta); } } } } else { ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent ); client->ps.speed = ent->NPC->desiredSpeed; } } else {//Client sets ucmds and such for speed alterations client->ps.speed = g_speed->value;//default is 320 } //Apply forced movement if ( client->forced_forwardmove ) { ucmd->forwardmove = client->forced_forwardmove; if ( !client->ps.speed ) { if ( ent->NPC != NULL ) { client->ps.speed = ent->NPC->stats.runSpeed; } else { client->ps.speed = g_speed->value;//default is 320 } } } if ( client->forced_rightmove ) { ucmd->rightmove = client->forced_rightmove; if ( !client->ps.speed ) { if ( ent->NPC != NULL ) { client->ps.speed = ent->NPC->stats.runSpeed; } else { client->ps.speed = g_speed->value;//default is 320 } } } //FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?) BG_AddPushVecToUcmd( ent, ucmd ); BG_CalculateOffsetAngles( ent, ucmd ); // set up for pmove oldEventSequence = client->ps.eventSequence; memset( &pm, 0, sizeof(pm) ); pm.gent = ent; pm.ps = &client->ps; pm.cmd = *ucmd; // pm.tracemask = MASK_PLAYERSOLID; // used differently for navgen pm.tracemask = ent->clipmask; pm.trace = gi.trace; pm.pointcontents = gi.pointcontents; pm.debugLevel = g_debugMove->integer; pm.noFootsteps = 0;//( g_dmflags->integer & DF_NO_FOOTSTEPS ) > 0; VectorCopy( client->ps.origin, oldOrigin ); // perform a pmove Pmove( &pm ); // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; { int seq; seq = (ent->client->ps.eventSequence-1) & (MAX_PS_EVENTS-1); ent->s.event = ent->client->ps.events[ seq ] | ( ( ent->client->ps.eventSequence & 3 ) << 8 ); ent->s.eventParm = ent->client->ps.eventParms[ seq ]; } } PlayerStateToEntityState( &ent->client->ps, &ent->s ); VectorCopy ( ent->currentOrigin, ent->lastOrigin ); #if 1 // use the precise origin for linking VectorCopy( ent->client->ps.origin, ent->currentOrigin ); #else //We don't use prediction anymore, so screw this // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); #endif VectorCopy (pm.mins, ent->mins); VectorCopy (pm.maxs, ent->maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; VectorCopy( ucmd->angles, client->pers.cmd_angles ); // execute client events ClientEvents( ent, oldEventSequence ); if ( pm.useEvent ) { //TODO: Use TryUse( ent ); } // link entity now, after any personal teleporters have been used gi.linkentity( ent ); ent->client->hiddenDist = 0; if ( !ent->client->noclip ) { G_TouchTriggersLerped( ent ); } // touch other objects ClientImpacts( ent, &pm ); // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( ent->NPC == NULL && level.time > client->respawnTime ) { // don't allow respawn if they are still flying through the // air, unless 10 extra seconds have passed, meaning something // strange is going on, like the corpse is caught in a wind tunnel if ( level.time < client->respawnTime + 10000 ) { if ( client->ps.groundEntityNum == ENTITYNUM_NONE ) { return; } } // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } if ((cg.missionStatusShow) && ((cg.missionStatusDeadTime + 1) < level.time)) { if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { cg.missionStatusShow = 0; ScoreBoardReset(); // Q3_TaskIDComplete( ent, TID_MISSIONSTATUS ); } } // perform once-a-second actions //ClientTimerActions( ent, msec ); //DEBUG INFO /* if ( client->ps.clientNum < 1 ) {//Only a player if ( ucmd->buttons & BUTTON_USE ) { NAV_PrintLocalWpDebugInfo( ent ); } } */ }