/* ------------------------- ImperialProbe_Wait ------------------------- */ void ImperialProbe_Wait(void) { if ( NPCInfo->localState == LSTATE_DROP ) { vec3_t endPos; trace_t trace; NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 ); VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 32 ); trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); if ( trace.fraction != 1.0f ) { G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN); } } NPC_UpdateAngles( qtrue, qtrue ); }
void R2D2_PartsMove( void ) { // Front 'eye' lense if ( TIMER_Done( NPC, "eyeDelay" ) ) { NPC->pos1.yaw = AngleNormalize360( NPC->pos1.yaw ); NPC->pos1.pitch += Q_irand( -20, 20 ); // Roll NPC->pos1.yaw = Q_irand( -20, 20 ); NPC->pos1.roll = Q_irand( -20, 20 ); /* if (NPC->genericBone1) { gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); } */ NPC_SetBoneAngles( NPC, "f_eye", &NPC->pos1 ); TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) ); } }
// TODO: Use new vector math library for all calculations. void ArmorComponent::HandleApplyDamageModifier(float& damage, Util::optional<Vec3> location, Util::optional<Vec3> direction, int flags, meansOfDeath_t meansOfDeath) { vec3_t origin, bulletPath, bulletAngle, locationRelativeToFloor, floor, normal; // TODO: Remove dependency on client. assert(entity.oldEnt->client); // Use non-regional damage where appropriate. if (flags & DAMAGE_NO_LOCDAMAGE || !location) { // TODO: Move G_GetNonLocDamageMod to ArmorComponent. damage *= GetNonLocationalDamageMod(); return; } // Get hit location relative to the floor beneath. if (g_unlagged.integer && entity.oldEnt->client->unlaggedCalc.used) { VectorCopy(entity.oldEnt->client->unlaggedCalc.origin, origin); } else { VectorCopy(entity.oldEnt->r.currentOrigin, origin); } BG_GetClientNormal(&entity.oldEnt->client->ps, normal); VectorMA(origin, entity.oldEnt->r.mins[2], normal, floor); VectorSubtract(location.value().Data(), floor, locationRelativeToFloor); // Get fraction of height where the hit landed. float height = entity.oldEnt->r.maxs[2] - entity.oldEnt->r.mins[2]; if (!height) height = 1.0f; float hitRatio = Math::Clamp(DotProduct(normal, locationRelativeToFloor) / VectorLength(normal), 0.0f, height) / height; // Get the yaw of the attack relative to the target's view yaw VectorSubtract(location.value().Data(), origin, bulletPath); vectoangles(bulletPath, bulletAngle); int hitRotation = AngleNormalize360(entity.oldEnt->client->ps.viewangles[YAW] - bulletAngle[YAW]); // Use regional modifier. // TODO: Move G_GetPointDamageMod to ArmorComponent. damage *= GetLocationalDamageMod(hitRotation, hitRatio); }
/* =============== G_CallSpawn Finds the spawn function for the entity and calls it, returning qfalse if not found =============== */ qboolean G_CallSpawn( gentity_t *ent ) { spawn_t *s; buildable_t buildable; if ( !ent->classname ) { G_Printf( "G_CallSpawn: NULL classname\n" ); return qfalse; } //check buildable spawn functions if ( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE ) { if ( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN ) { ent->s.angles[ YAW ] += 180.0f; AngleNormalize360( ent->s.angles[ YAW ] ); } G_SpawnBuildable( ent, buildable ); return qtrue; } // check normal spawn functions for ( s = spawns; s->name; s++ ) { if ( !strcmp( s->name, ent->classname ) ) { // found it s->spawn( ent ); return qtrue; } } G_Printf( "%s doesn't have a spawn function\n", ent->classname ); return qfalse; }
/* =============== G_CallSpawn Finds the spawn function for the entity and calls it, returning qfalse if not found =============== */ qboolean G_CallSpawn(gentity_t *ent) { spawn_t *s; buildable_t buildable; if (!ent->classname) { G_Printf("G_CallSpawn: NULL classname\n"); return qfalse; } //check buildable spawn functions if ((buildable = BG_FindBuildNumForEntityName(ent->classname)) != BA_NONE) { // don't spawn built-in buildings if we are using a custom layout if (level.layout[ 0 ] && Q_stricmp(level.layout, "*BUILTIN*")) return qtrue; if (buildable == BA_A_SPAWN || buildable == BA_H_SPAWN) { ent->s.angles[ YAW ] += 180.0f; AngleNormalize360(ent->s.angles[ YAW ]); } G_SpawnBuildable(ent, buildable, ent->biteam, level.survivalStage); return qtrue; } // check normal spawn functions for (s = spawns; s->name; s++) { if (!strcmp(s->name, ent->classname)) { // found it s->spawn(ent); return qtrue; } } G_Printf("%s doesn't have a spawn function\n", ent->classname); return qfalse; }
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 Pilot_Steer_Vehicle() { if (!NPC->enemy || !NPC->enemy->client) { return; } // SETUP //======= // Setup Actor Data //------------------ CVec3 ActorPos(NPC->currentOrigin); CVec3 ActorAngles(NPC->currentAngles); ActorAngles[2] = 0; Vehicle_t* ActorVeh = NPCInfo->greetEnt->m_pVehicle; bool ActorInTurbo = (ActorVeh->m_iTurboTime>level.time); float ActorSpeed = (ActorVeh)?(VectorLength(ActorVeh->m_pParentEntity->client->ps.velocity)):(NPC->client->ps.speed); // If my vehicle is spinning out of control, just hold on, we're going to die!!!!! //--------------------------------------------------------------------------------- if (ActorVeh && (ActorVeh->m_ulFlags & VEH_OUTOFCONTROL)) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons &=~BUTTON_ATTACK; ucmd.buttons &=~BUTTON_ALT_ATTACK; return; } CVec3 ActorDirection; AngleVectors(ActorAngles.v, ActorDirection.v, 0, 0); CVec3 ActorFuturePos(ActorPos); ActorFuturePos.ScaleAdd(ActorDirection, FUTURE_PRED_DIST); bool ActorDoTurbo = false; bool ActorAccelerate = false; bool ActorAimAtTarget= true; float ActorYawOffset = 0.0f; // Setup Enemy Data //------------------ CVec3 EnemyPos(NPC->enemy->currentOrigin); CVec3 EnemyAngles(NPC->enemy->currentAngles); EnemyAngles[2] = 0; Vehicle_t* EnemyVeh = (NPC->enemy->s.m_iVehicleNum)?(g_entities[NPC->enemy->s.m_iVehicleNum].m_pVehicle):(0); bool EnemyInTurbo = (EnemyVeh && EnemyVeh->m_iTurboTime>level.time); float EnemySpeed = (EnemyVeh)?(EnemyVeh->m_pParentEntity->client->ps.speed):(NPC->enemy->resultspeed); bool EnemySlideBreak = (EnemyVeh && (EnemyVeh->m_ulFlags&VEH_SLIDEBREAKING || EnemyVeh->m_ulFlags&VEH_STRAFERAM)); bool EnemyDead = (NPC->enemy->health<=0); bool ActorFlank = (NPCInfo->lastAvoidSteerSideDebouncer>level.time && EnemyVeh && EnemySpeed>10.0f); CVec3 EnemyDirection; CVec3 EnemyRight; AngleVectors(EnemyAngles.v, EnemyDirection.v, EnemyRight.v, 0); CVec3 EnemyFuturePos(EnemyPos); EnemyFuturePos.ScaleAdd(EnemyDirection, FUTURE_PRED_DIST); ESide EnemySide = ActorPos.LRTest(EnemyPos, EnemyFuturePos); CVec3 EnemyFlankPos(EnemyFuturePos); EnemyFlankPos.ScaleAdd(EnemyRight, (EnemySide==Side_Right)?(FUTURE_SIDE_DIST):(-FUTURE_SIDE_DIST)); // Debug Draw Enemy Data //----------------------- if (false) { CG_DrawEdge(EnemyPos.v, EnemyFuturePos.v, EDGE_IMPACT_SAFE); CG_DrawEdge(EnemyFuturePos.v, EnemyFlankPos.v, EDGE_IMPACT_SAFE); } // Setup Move And Aim Directions //------------------------------- CVec3 MoveDirection((ActorFlank)?(EnemyFlankPos):(EnemyFuturePos)); MoveDirection -= ActorPos; float MoveDistance = MoveDirection.SafeNorm(); float MoveAccuracy = MoveDirection.Dot(ActorDirection); CVec3 AimDirection(EnemyPos); AimDirection -= ActorPos; float AimDistance = AimDirection.SafeNorm(); float AimAccuracy = AimDirection.Dot(ActorDirection); if (!ActorFlank && TIMER_Done(NPC, "FlankAttackCheck")) { TIMER_Set(NPC, "FlankAttackCheck", Q_irand(1000, 3000)); if (MoveDistance<4000 && Q_irand(0, 1)==0) { NPCInfo->lastAvoidSteerSideDebouncer = level.time + Q_irand(8000, 14000); } } // Fly By Sounds //--------------- if ((ActorVeh->m_pVehicleInfo->soundFlyBy || ActorVeh->m_pVehicleInfo->soundFlyBy2) && EnemyVeh && MoveDistance<800 && ActorSpeed>500.0f && TIMER_Done(NPC, "FlybySoundDebouncer") ) { if (EnemySpeed<100.0f || (ActorDirection.Dot(EnemyDirection)*(MoveDistance/800.0f))<-0.5f) { TIMER_Set(NPC, "FlybySoundDebouncer", 2000); int soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy; if (ActorVeh->m_pVehicleInfo->soundFlyBy2 && (!soundFlyBy || !Q_irand(0,1))) { soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy2; } G_Sound(ActorVeh->m_pParentEntity, soundFlyBy); } } // FLY PAST BEHAVIOR //=================== if (EnemySlideBreak || !TIMER_Done(NPC, "MinHoldDirectionTime")) { if (TIMER_Done(NPC, "MinHoldDirectionTime")) { TIMER_Set(NPC, "MinHoldDirectionTime", 500); // Hold For At Least 500 ms } ActorAccelerate = true; // Go ActorAimAtTarget = false; // Don't Alter Our Aim Direction ucmd.buttons &=~BUTTON_VEH_SPEED; // Let Normal Vehicle Controls Go } // FLANKING BEHAVIOR //=================== else if (ActorFlank) { ActorAccelerate = true; ActorDoTurbo = (MoveDistance>2500 || EnemyInTurbo); ucmd.buttons |= BUTTON_VEH_SPEED; // Tells PMove to use the ps.speed we calculate here, not the one from g_vehicles.c // For Flanking, We Calculate The Speed By Hand, Rather Than Using Pure Accelerate / No Accelerate Functionality //--------------------------------------------------------------------------------------------------------------- NPC->client->ps.speed = ActorVeh->m_pVehicleInfo->speedMax * ((ActorInTurbo)?(1.35f):(1.15f)); // If In Slowing Distance, Scale Down The Speed As We Approach Our Move Target //----------------------------------------------------------------------------- if (MoveDistance<ATTACK_FLANK_SLOWING) { NPC->client->ps.speed *= (MoveDistance/ATTACK_FLANK_SLOWING); NPC->client->ps.speed += EnemySpeed; // Match Enemy Speed //------------------- if (NPC->client->ps.speed<5.0f && EnemySpeed<5.0f) { NPC->client->ps.speed = EnemySpeed; } // Extra Slow Down When Out In Front //----------------------------------- if (MoveAccuracy<0.0f) { NPC->client->ps.speed *= (MoveAccuracy + 1.0f); } MoveDirection *= (MoveDistance/ATTACK_FLANK_SLOWING); EnemyDirection *= 1.0f - (MoveDistance/ATTACK_FLANK_SLOWING); MoveDirection += EnemyDirection; if (TIMER_Done(NPC, "RamCheck")) { TIMER_Set(NPC, "RamCheck", Q_irand(1000, 3000)); if (MoveDistance<RAM_DIST && Q_irand(0, 2)==0) { VEH_StartStrafeRam(ActorVeh, (EnemySide==Side_Left)); } } } } // NORMAL CHASE BEHAVIOR //======================= else { if (!EnemyVeh && AimAccuracy>0.99f && MoveDistance<500 && !EnemyDead) { ActorAccelerate = true; ActorDoTurbo = false; } else { ActorAccelerate = ((MoveDistance>500 && EnemySpeed>20.0f) || MoveDistance>1000); ActorDoTurbo = (MoveDistance>3000 && EnemySpeed>20.0f); } ucmd.buttons &=~BUTTON_VEH_SPEED; } // APPLY RESULTS //======================= // Decide Turbo //-------------- if (ActorDoTurbo || ActorInTurbo) { ucmd.buttons |= BUTTON_ALT_ATTACK; } else { ucmd.buttons &=~BUTTON_ALT_ATTACK; } // Decide Acceleration //--------------------- ucmd.forwardmove = (ActorAccelerate)?(127):(0); // Decide To Shoot //----------------- ucmd.buttons &=~BUTTON_ATTACK; ucmd.rightmove = 0; if (AimDistance<2000 && !EnemyDead) { // If Doing A Ram Attack //----------------------- if (ActorYawOffset!=0) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons &=~BUTTON_ATTACK; } else if (AimAccuracy>ATTACK_FWD) { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } ucmd.buttons |= BUTTON_ATTACK; } else if (AimAccuracy<AIM_SIDE && AimAccuracy>-AIM_SIDE) { if (NPC->client->ps.weapon!=WP_BLASTER) { NPC_ChangeWeapon(WP_BLASTER); } if (AimAccuracy<ATTACK_SIDE && AimAccuracy>-ATTACK_SIDE) { //if (!TIMER_Done(NPC, "RiderAltAttack")) //{ // ucmd.buttons |= BUTTON_ALT_ATTACK; //} //else //{ ucmd.buttons |= BUTTON_ATTACK; /* if (TIMER_Done(NPC, "RiderAltAttackCheck")) { TIMER_Set(NPC, "RiderAltAttackCheck", Q_irand(1000, 3000)); if (Q_irand(0, 2)==0) { TIMER_Set(NPC, "RiderAltAttack", 300); } }*/ //} WeaponThink(true); } ucmd.rightmove = (EnemySide==Side_Left)?( 127):(-127); } else { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } } } else { if (NPC->client->ps.weapon!=WP_NONE) { NPC_ChangeWeapon(WP_NONE); } } // Aim At Target //--------------- if (ActorAimAtTarget) { MoveDirection.VecToAng(); NPCInfo->desiredPitch = AngleNormalize360(MoveDirection[PITCH]); NPCInfo->desiredYaw = AngleNormalize360(MoveDirection[YAW] + ActorYawOffset); } NPC_UpdateAngles(qtrue, qtrue); }
/* ------------------------- Interrogator_PartsMove ------------------------- */ void Interrogator_PartsMove(void) { // Syringe if ( TIMER_Done(NPC,"syringeDelay") ) { NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); if ((NPC->pos1[1] < 60) || (NPC->pos1[1] > 300)) { NPC->pos1[1]+=Q_irand( -20, 20 ); // Pitch } else if (NPC->pos1[1] > 180) { NPC->pos1[1]=Q_irand( 300, 360 ); // Pitch } else { NPC->pos1[1]=Q_irand( 0, 60 ); // Pitch } // gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); NPC_SetBoneAngles(NPC, "left_arm", NPC->pos1); TIMER_Set( NPC, "syringeDelay", Q_irand( 100, 1000 ) ); } // Scalpel if ( TIMER_Done(NPC,"scalpelDelay") ) { // Change pitch if ( NPCInfo->localState == LSTATE_BLADEDOWN ) // Blade is moving down { NPC->pos2[0]-= 30; if (NPC->pos2[0] < 180) { NPC->pos2[0] = 180; NPCInfo->localState = LSTATE_BLADEUP; // Make it move up } } else // Blade is coming back up { NPC->pos2[0]+= 30; if (NPC->pos2[0] >= 360) { NPC->pos2[0] = 360; NPCInfo->localState = LSTATE_BLADEDOWN; // Make it move down TIMER_Set( NPC, "scalpelDelay", Q_irand( 100, 1000 ) ); } } NPC->pos2[0] = AngleNormalize360( NPC->pos2[0]); // gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone2, NPC->pos2, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); NPC_SetBoneAngles(NPC, "right_arm", NPC->pos2); } // Claw NPC->pos3[1] += Q_irand( 10, 30 ); NPC->pos3[1] = AngleNormalize360( NPC->pos3[1]); //gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone3, NPC->pos3, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); NPC_SetBoneAngles(NPC, "claw", NPC->pos3); }
void Sniper_FaceEnemy( void ) { //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me? //FIXME: need to give designers option to make them not miss first few shots if ( NPC->enemy ) { vec3_t muzzle, target, angles, forward, right, up; //Get the positions AngleVectors( NPC->client->ps.viewangles, forward, right, up ); CalcMuzzlePoint( NPC, forward, right, up, muzzle, 0 ); //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target ); if ( enemyDist > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128) { if ( NPC->count < (5-NPCInfo->stats.aim) ) {//miss a few times first if ( shoot && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime ) {//ready to fire again qboolean aimError = qfalse; qboolean hit = qtrue; int tryMissCount = 0; trace_t trace; GetAnglesForDirection( muzzle, target, angles ); AngleVectors( angles, forward, right, up ); while ( hit && tryMissCount < 10 ) { tryMissCount++; if ( !Q_irand( 0, 1 ) ) { aimError = qtrue; if ( !Q_irand( 0, 1 ) ) { VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), right, target ); } else { VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), right, target ); } } if ( !aimError || !Q_irand( 0, 1 ) ) { if ( !Q_irand( 0, 1 ) ) { VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), up, target ); } else { VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), up, target ); } } gi.trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT, G2_NOCOLLIDE, 0 ); hit = Sniper_EvaluateShot( trace.entityNum ); } NPC->count++; } else { if ( !enemyLOS ) { NPC_UpdateAngles( qtrue, qtrue ); return; } } } else {//based on distance, aim value, difficulty and enemy movement, miss //FIXME: incorporate distance as a factor? int missFactor = 8-(NPCInfo->stats.aim+g_spskill->integer) * 3; if ( missFactor > ENEMY_POS_LAG_STEPS ) { missFactor = ENEMY_POS_LAG_STEPS; } else if ( missFactor < 0 ) {//??? missFactor = 0 ; } VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target ); } GetAnglesForDirection( muzzle, target, angles ); } else { target[2] += Q_flrand( 0, NPC->enemy->maxs[2] ); //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target ); GetAnglesForDirection( muzzle, target, angles ); } NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); } NPC_UpdateAngles( qtrue, qtrue ); }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; //if client is dead and following teammate, stop following before spawning if( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) client->sess.spectatorState = SPECTATOR_LOCKED; // if client is dead and following teammate, stop following before spawning if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); if( origin != NULL ) VectorCopy( origin, spawn_origin ); if( angles != NULL ) VectorCopy( angles, spawn_angles ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if( client->sess.spectatorState != SPECTATOR_NOT ) { if( teamLocal == TEAM_NONE ) spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_ALIENS ) spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); else if( teamLocal == TEAM_HUMANS ) spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } else { if( spawn == NULL ) { G_Error( "ClientSpawn: spawn is NULL\n" ); return; } spawnPoint = spawn; if( ent != spawn ) { //start spawn animation on spawnPoint G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); if( spawnPoint->buildableTeam == TEAM_ALIENS ) spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; else if( spawnPoint->buildableTeam == TEAM_HUMANS ) spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) client->ps.persistant[ i ] = persistant[ i ]; client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; // calculate each client's acceleration ent->evaluateAcceleration = qtrue; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); if( client->sess.spectatorState == SPECTATOR_NOT ) client->ps.stats[ STAT_MAX_HEALTH ] = BG_Class( ent->client->pers.classSelection )->health; else client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if( client->sess.spectatorState == SPECTATOR_NOT ) weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); // health will count down towards max_health ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; //if evolving scale health if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); #define UP_VEL 150.0f #define F_VEL 50.0f //give aliens some spawn velocity if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if( client->sess.spectatorState == SPECTATOR_NOT && client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if( client->pers.teamSelection == TEAM_HUMANS ) G_ForceWeaponChange( ent, weapon ); client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if( level.intermissiontime ) MoveClientToIntermission( ent ); else { // fire the targets of the spawn point if( !spawn ) G_UseTargets( spawnPoint, ent ); client->ps.weapon = client->ps.stats[ STAT_WEAPON ]; } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); client->pers.infoChangeTime = level.time; }
qboolean NPC_MoveToGoal( qboolean tryStraight ) { float distance; vec3_t dir; #if AI_TIMERS int startTime = GetTime(0); #endif// AI_TIMERS //If taking full body pain, don't move if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) { return qtrue; } /* if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) {//If in an emplaced gun, never try to navigate! return qtrue; } */ //rwwFIXMEFIXME: emplaced support //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least? //Get our movement direction #if 1 if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse ) #else if ( NPC_GetMoveDirection( dir, &distance ) == qfalse ) #endif return qfalse; NPCInfo->distToGoal = distance; //Convert the move to angles vectoangles( dir, NPCInfo->lastPathAngles ); if ( (ucmd.buttons&BUTTON_WALKING) ) { NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } else { NPC->client->ps.speed = NPCInfo->stats.runSpeed; } //FIXME: still getting ping-ponging in certain cases... !!! Nav/avoidance error? WTF???!!! //If in combat move, then move directly towards our goal if ( NPC_CheckCombatMove() ) {//keep current facing G_UcmdMoveForDir( NPC, &ucmd, dir ); } else {//face our goal //FIXME: strafe instead of turn if change in dir is small and temporary NPCInfo->desiredPitch = 0.0f; NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->lastPathAngles[YAW] ); //Pitch towards the goal and also update if flying or swimming if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) { NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] ); if ( dir[2] ) { float scale = (dir[2] * distance); if ( scale > 64 ) { scale = 64; } else if ( scale < -64 ) { scale = -64; } NPC->client->ps.velocity[2] = scale; //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64; } } //Set any final info ucmd.forwardmove = 127; } #if AI_TIMERS navTime += GetTime( startTime ); #endif// AI_TIMERS return qtrue; }
/* ------------------------- NAVNEW_SidestepBlocker ------------------------- */ qboolean NAVNEW_SidestepBlocker( gentity_t *self, gentity_t *blocker, vec3_t blocked_dir, float blocked_dist, vec3_t movedir, vec3_t right ) {//trace to sides of blocker and see if either is clear trace_t tr; vec3_t avoidAngles; vec3_t avoidRight_dir, avoidLeft_dir, block_pos, mins; float rightSucc, leftSucc, yaw, avoidRadius, arcAngle; VectorCopy( self->r.mins, mins ); mins[2] += STEPSIZE; //Get the blocked direction yaw = vectoyaw( blocked_dir ); //Get the avoid radius avoidRadius = sqrt( ( blocker->r.maxs[0] * blocker->r.maxs[0] ) + ( blocker->r.maxs[1] * blocker->r.maxs[1] ) ) + sqrt( ( self->r.maxs[0] * self->r.maxs[0] ) + ( self->r.maxs[1] * self->r.maxs[1] ) ); //See if we're inside our avoidance radius arcAngle = ( blocked_dist <= avoidRadius ) ? 135 : ( ( avoidRadius / blocked_dist ) * 90 ); /* float dot = DotProduct( blocked_dir, right ); //Go right on the first try if that works better if ( dot < 0.0f ) arcAngle *= -1; */ VectorClear( avoidAngles ); //need to stop it from ping-ponging, so we have a bit of a debounce time on which side you try if ( self->NPC->sideStepHoldTime > level.time ) { if ( self->NPC->lastSideStepSide == -1 )//left { arcAngle *= -1; }//else right avoidAngles[YAW] = AngleNormalize360( yaw + arcAngle ); AngleVectors( avoidAngles, movedir, NULL, NULL ); VectorMA( self->r.currentOrigin, blocked_dist, movedir, block_pos ); trap->Trace( &tr, self->r.currentOrigin, mins, self->r.maxs, block_pos, self->s.number, self->clipmask|CONTENTS_BOTCLIP, qfalse, 0, 0 ); return (tr.fraction==1.0&&!tr.allsolid&&!tr.startsolid); } //test right avoidAngles[YAW] = AngleNormalize360( yaw + arcAngle ); AngleVectors( avoidAngles, avoidRight_dir, NULL, NULL ); VectorMA( self->r.currentOrigin, blocked_dist, avoidRight_dir, block_pos ); trap->Trace( &tr, self->r.currentOrigin, mins, self->r.maxs, block_pos, self->s.number, self->clipmask|CONTENTS_BOTCLIP, qfalse, 0, 0 ); if ( !tr.allsolid && !tr.startsolid ) { if ( tr.fraction >= 1.0f ) {//all clear, go for it (favor the right if both are equal) VectorCopy( avoidRight_dir, movedir ); self->NPC->lastSideStepSide = 1; self->NPC->sideStepHoldTime = level.time + 2000; return qtrue; } rightSucc = tr.fraction; } else { rightSucc = 0.0f; } //now test left arcAngle *= -1; avoidAngles[YAW] = AngleNormalize360( yaw + arcAngle ); AngleVectors( avoidAngles, avoidLeft_dir, NULL, NULL ); VectorMA( self->r.currentOrigin, blocked_dist, avoidLeft_dir, block_pos ); trap->Trace( &tr, self->r.currentOrigin, mins, self->r.maxs, block_pos, self->s.number, self->clipmask|CONTENTS_BOTCLIP, qfalse, 0, 0 ); if ( !tr.allsolid && !tr.startsolid ) { if ( tr.fraction >= 1.0f ) {//all clear, go for it (right side would have already succeeded if as good as this) VectorCopy( avoidLeft_dir, movedir ); self->NPC->lastSideStepSide = -1; self->NPC->sideStepHoldTime = level.time + 2000; return qtrue; } leftSucc = tr.fraction; } else { leftSucc = 0.0f; } if ( leftSucc == 0.0f && rightSucc == 0.0f ) {//both sides failed return qfalse; } if ( rightSucc*blocked_dist >= avoidRadius || leftSucc*blocked_dist >= avoidRadius ) {//the traces hit something, but got a relatively good distance if ( rightSucc >= leftSucc ) {//favor the right, all things being equal VectorCopy( avoidRight_dir, movedir ); self->NPC->lastSideStepSide = 1; self->NPC->sideStepHoldTime = level.time + 2000; } else { VectorCopy( avoidLeft_dir, movedir ); self->NPC->lastSideStepSide = -1; self->NPC->sideStepHoldTime = level.time + 2000; } return qtrue; } //if neither are enough, we probably can't get around him return qfalse; }
/* * CG_Democam_CalcView */ static int CG_Democam_CalcView( void ) { int i, viewType; float lerpfrac; vec3_t v; viewType = VIEWDEF_PLAYERVIEW; VectorClear( cam_velocity ); if( currentcam ) { if( !nextcam ) { lerpfrac = 0; } else { lerpfrac = (float)( demo_time - currentcam->timeStamp ) / (float)( nextcam->timeStamp - currentcam->timeStamp ); } switch( currentcam->type ) { case DEMOCAM_FIRSTPERSON: VectorCopy( cg.view.origin, cam_origin ); VectorCopy( cg.view.angles, cam_angles ); VectorCopy( cg.view.velocity, cam_velocity ); cam_fov = cg.view.fov_y; break; case DEMOCAM_THIRDPERSON: VectorCopy( cg.view.origin, cam_origin ); VectorCopy( cg.view.angles, cam_angles ); VectorCopy( cg.view.velocity, cam_velocity ); cam_fov = cg.view.fov_y; cam_3dPerson = true; break; case DEMOCAM_POSITIONAL: viewType = VIEWDEF_DEMOCAM; cam_POVent = 0; VectorCopy( currentcam->origin, cam_origin ); if( !CG_DemoCam_LookAt( currentcam->trackEnt, cam_origin, cam_angles ) ) { VectorCopy( currentcam->angles, cam_angles ); } cam_fov = currentcam->fov; break; case DEMOCAM_PATH_LINEAR: viewType = VIEWDEF_DEMOCAM; cam_POVent = 0; VectorCopy( cam_origin, v ); if( !nextcam || nextcam->type == DEMOCAM_FIRSTPERSON || nextcam->type == DEMOCAM_THIRDPERSON ) { CG_Printf( "Warning: CG_DemoCam: path_linear cam without a valid next cam\n" ); VectorCopy( currentcam->origin, cam_origin ); if( !CG_DemoCam_LookAt( currentcam->trackEnt, cam_origin, cam_angles ) ) { VectorCopy( currentcam->angles, cam_angles ); } cam_fov = currentcam->fov; } else { VectorLerp( currentcam->origin, lerpfrac, nextcam->origin, cam_origin ); if( !CG_DemoCam_LookAt( currentcam->trackEnt, cam_origin, cam_angles ) ) { for( i = 0; i < 3; i++ ) cam_angles[i] = LerpAngle( currentcam->angles[i], nextcam->angles[i], lerpfrac ); } cam_fov = (float)currentcam->fov + (float)( nextcam->fov - currentcam->fov ) * lerpfrac; } // set velocity VectorSubtract( cam_origin, v, cam_velocity ); VectorScale( cam_velocity, 1.0f / (float)cg.frameTime, cam_velocity ); break; case DEMOCAM_PATH_SPLINE: viewType = VIEWDEF_DEMOCAM; cam_POVent = 0; clamp( lerpfrac, 0, 1 ); VectorCopy( cam_origin, v ); if( !nextcam || nextcam->type == DEMOCAM_FIRSTPERSON || nextcam->type == DEMOCAM_THIRDPERSON ) { CG_Printf( "Warning: CG_DemoCam: path_spline cam without a valid next cam\n" ); VectorCopy( currentcam->origin, cam_origin ); if( !CG_DemoCam_LookAt( currentcam->trackEnt, cam_origin, cam_angles ) ) { VectorCopy( currentcam->angles, cam_angles ); } cam_fov = currentcam->fov; } else { // valid spline path #define VectorHermiteInterp( a, at, b, bt, c, v ) ( ( v )[0] = ( 2 * pow( c, 3 ) - 3 * pow( c, 2 ) + 1 ) * a[0] + ( pow( c, 3 ) - 2 * pow( c, 2 ) + c ) * 2 * at[0] + ( -2 * pow( c, 3 ) + 3 * pow( c, 2 ) ) * b[0] + ( pow( c, 3 ) - pow( c, 2 ) ) * 2 * bt[0], ( v )[1] = ( 2 * pow( c, 3 ) - 3 * pow( c, 2 ) + 1 ) * a[1] + ( pow( c, 3 ) - 2 * pow( c, 2 ) + c ) * 2 * at[1] + ( -2 * pow( c, 3 ) + 3 * pow( c, 2 ) ) * b[1] + ( pow( c, 3 ) - pow( c, 2 ) ) * 2 * bt[1], ( v )[2] = ( 2 * pow( c, 3 ) - 3 * pow( c, 2 ) + 1 ) * a[2] + ( pow( c, 3 ) - 2 * pow( c, 2 ) + c ) * 2 * at[2] + ( -2 * pow( c, 3 ) + 3 * pow( c, 2 ) ) * b[2] + ( pow( c, 3 ) - pow( c, 2 ) ) * 2 * bt[2] ) float lerpspline, A, B, C, n1, n2, n3; cg_democam_t *previouscam = NULL; cg_democam_t *secondnextcam = NULL; if( nextcam ) { secondnextcam = CG_Democam_FindNext( nextcam->timeStamp ); } if( currentcam->timeStamp > 0 ) { previouscam = CG_Democam_FindCurrent( currentcam->timeStamp - 1 ); } if( !previouscam && nextcam && !secondnextcam ) { lerpfrac = (float)( demo_time - currentcam->timeStamp ) / (float)( nextcam->timeStamp - currentcam->timeStamp ); lerpspline = lerpfrac; } else if( !previouscam && nextcam && secondnextcam ) { n1 = nextcam->timeStamp - currentcam->timeStamp; n2 = secondnextcam->timeStamp - nextcam->timeStamp; A = n1 * ( n1 - n2 ) / ( pow( n1, 2 ) + n1 * n2 - n1 - n2 ); B = ( 2 * n1 * n2 - n1 - n2 ) / ( pow( n1, 2 ) + n1 * n2 - n1 - n2 ); lerpfrac = (float)( demo_time - currentcam->timeStamp ) / (float)( nextcam->timeStamp - currentcam->timeStamp ); lerpspline = A * pow( lerpfrac, 2 ) + B * lerpfrac; } else if( previouscam && nextcam && !secondnextcam ) { n2 = currentcam->timeStamp - previouscam->timeStamp; n3 = nextcam->timeStamp - currentcam->timeStamp; A = n3 * ( n2 - n3 ) / ( -n2 - n3 + n2 * n3 + pow( n3, 2 ) ); B = -1 / ( -n2 - n3 + n2 * n3 + pow( n3, 2 ) ) * ( n2 + n3 - 2 * pow( n3, 2 ) ); lerpfrac = (float)( demo_time - currentcam->timeStamp ) / (float)( nextcam->timeStamp - currentcam->timeStamp ); lerpspline = A * pow( lerpfrac, 2 ) + B * lerpfrac; } else if( previouscam && nextcam && secondnextcam ) { n1 = currentcam->timeStamp - previouscam->timeStamp; n2 = nextcam->timeStamp - currentcam->timeStamp; n3 = secondnextcam->timeStamp - nextcam->timeStamp; A = -2 * pow( n2, 2 ) * ( -pow( n2, 2 ) + n1 * n3 ) / ( 2 * n2 * n3 + pow( n2, 3 ) * n3 - 3 * pow( n2, 2 ) * n1 + n1 * pow( n2, 3 ) + 2 * n1 * n2 - 3 * pow( n2, 2 ) * n3 - 3 * pow( n2, 3 ) + 2 * pow( n2, 2 ) + pow( n2, 4 ) + n1 * pow( n2, 2 ) * n3 - 3 * n1 * n2 * n3 + 2 * n1 * n3 ); B = pow( n2, 2 ) * ( -2 * n1 - 3 * pow( n2, 2 ) - n2 * n3 + 2 * n3 + 3 * n1 * n3 + n1 * n2 ) / ( 2 * n2 * n3 + pow( n2, 3 ) * n3 - 3 * pow( n2, 2 ) * n1 + n1 * pow( n2, 3 ) + 2 * n1 * n2 - 3 * pow( n2, 2 ) * n3 - 3 * pow( n2, 3 ) + 2 * pow( n2, 2 ) + pow( n2, 4 ) + n1 * pow( n2, 2 ) * n3 - 3 * n1 * n2 * n3 + 2 * n1 * n3 ); C = -( pow( n2, 2 ) * n1 - 2 * n1 * n2 + 3 * n1 * n2 * n3 - 2 * n1 * n3 - 2 * pow( n2, 4 ) + 3 * pow( n2, 3 ) - 2 * pow( n2, 3 ) * n3 + 5 * pow( n2, 2 ) * n3 - 2 * pow( n2, 2 ) - 2 * n2 * n3 ) / ( 2 * n2 * n3 + pow( n2, 3 ) * n3 - 3 * pow( n2, 2 ) * n1 + n1 * pow( n2, 3 ) + 2 * n1 * n2 - 3 * pow( n2, 2 ) * n3 - 3 * pow( n2, 3 ) + 2 * pow( n2, 2 ) + pow( n2, 4 ) + n1 * pow( n2, 2 ) * n3 - 3 * n1 * n2 * n3 + 2 * n1 * n3 ); lerpfrac = (float)( demo_time - currentcam->timeStamp ) / (float)( nextcam->timeStamp - currentcam->timeStamp ); lerpspline = A * pow( lerpfrac, 3 ) + B * pow( lerpfrac, 2 ) + C * lerpfrac; } else { lerpfrac = 0; lerpspline = 0; } VectorHermiteInterp( currentcam->origin, currentcam->tangent, nextcam->origin, nextcam->tangent, lerpspline, cam_origin ); if( !CG_DemoCam_LookAt( currentcam->trackEnt, cam_origin, cam_angles ) ) { VectorHermiteInterp( currentcam->angles, currentcam->angles_tangent, nextcam->angles, nextcam->angles_tangent, lerpspline, cam_angles ); } cam_fov = (float)currentcam->fov + (float)( nextcam->fov - currentcam->fov ) * lerpfrac; #undef VectorHermiteInterp } // set velocity VectorSubtract( cam_origin, v, cam_velocity ); VectorScale( cam_velocity, 1.0f / (float)cg.frameTime, cam_velocity ); break; case DEMOCAM_ORBITAL: viewType = VIEWDEF_DEMOCAM; cam_POVent = 0; cam_fov = currentcam->fov; VectorCopy( cam_origin, v ); if( !currentcam->trackEnt || currentcam->trackEnt >= MAX_EDICTS ) { CG_Printf( "Warning: CG_DemoCam: orbital cam needs a track entity set\n" ); VectorCopy( currentcam->origin, cam_origin ); VectorClear( cam_angles ); VectorClear( cam_velocity ); } else { vec3_t center, forward; struct cmodel_s *cmodel; const float ft = (float)cg.frameTime * 0.001f; // find the trackEnt origin VectorLerp( cg_entities[currentcam->trackEnt].prev.origin, cg.lerpfrac, cg_entities[currentcam->trackEnt].current.origin, center ); // if having a bounding box, look to its center if( ( cmodel = CG_CModelForEntity( currentcam->trackEnt ) ) != NULL ) { vec3_t mins, maxs; trap_CM_InlineModelBounds( cmodel, mins, maxs ); for( i = 0; i < 3; i++ ) center[i] += ( mins[i] + maxs[i] ); } if( !cam_orbital_radius ) { // cam is just started, find distance from cam to trackEnt and keep it as radius VectorSubtract( currentcam->origin, center, forward ); cam_orbital_radius = VectorNormalize( forward ); VecToAngles( forward, cam_orbital_angles ); } for( i = 0; i < 3; i++ ) { cam_orbital_angles[i] += currentcam->angles[i] * ft; cam_orbital_angles[i] = AngleNormalize360( cam_orbital_angles[i] ); } AngleVectors( cam_orbital_angles, forward, NULL, NULL ); VectorMA( center, cam_orbital_radius, forward, cam_origin ); // lookat VectorInverse( forward ); VecToAngles( forward, cam_angles ); } // set velocity VectorSubtract( cam_origin, v, cam_velocity ); VectorScale( cam_velocity, 1.0f / ( cg.frameTime * 1000.0f ), cam_velocity ); break; default: break; } if( currentcam->type != DEMOCAM_ORBITAL ) { VectorClear( cam_orbital_angles ); cam_orbital_radius = 0; } } return viewType; }
//----------------------------------------------------- static void turretG2_aim( gentity_t *self ) //----------------------------------------------------- { vec3_t enemyDir, org, org2; vec3_t desiredAngles, setAngle; float diffYaw = 0.0f, diffPitch = 0.0f; float maxYawSpeed = (self->spawnflags&SPF_TURRETG2_TURBO)?30.0f:14.0f; float maxPitchSpeed = (self->spawnflags&SPF_TURRETG2_TURBO)?15.0f:3.0f; // move our gun base yaw to where we should be at this time.... BG_EvaluateTrajectory( &self->s.apos, level.time, self->r.currentAngles ); self->r.currentAngles[YAW] = AngleNormalize360( self->r.currentAngles[YAW] ); self->speed = AngleNormalize360( self->speed ); if ( self->enemy ) { mdxaBone_t boltMatrix; // ...then we'll calculate what new aim adjustments we should attempt to make this frame // Aim at enemy if ( self->enemy->client ) { VectorCopy( self->enemy->client->renderInfo.eyePoint, org ); } else { VectorCopy( self->enemy->r.currentOrigin, org ); } if ( self->spawnflags & 2 ) { org[2] -= 15; } else { org[2] -= 5; } if ( (self->spawnflags&SPF_TURRETG2_LEAD_ENEMY) ) { //we want to lead them a bit vec3_t diff, velocity; float dist; VectorSubtract( org, self->s.origin, diff ); dist = VectorNormalize( diff ); if ( self->enemy->client ) { VectorCopy( self->enemy->client->ps.velocity, velocity ); } else { VectorCopy( self->enemy->s.pos.trDelta, velocity ); } VectorMA( org, (dist/self->mass), velocity, org ); } // Getting the "eye" here trap_G2API_GetBoltMatrix( self->ghoul2, 0, (self->alt_fire?self->genericValue12:self->genericValue11), &boltMatrix, self->r.currentAngles, self->s.origin, level.time, NULL, self->modelScale ); BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org2 ); VectorSubtract( org, org2, enemyDir ); vectoangles( enemyDir, desiredAngles ); diffYaw = AngleSubtract( self->r.currentAngles[YAW], desiredAngles[YAW] ); diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] ); } else { // no enemy, so make us slowly sweep back and forth as if searching for a new one // diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f; // don't do this for now since it can make it go into walls. } if ( diffYaw ) { // cap max speed.... if ( fabs(diffYaw) > maxYawSpeed ) { diffYaw = ( diffYaw >= 0 ? maxYawSpeed : -maxYawSpeed ); } // ...then set up our desired yaw VectorSet( setAngle, 0.0f, diffYaw, 0.0f ); VectorCopy( self->r.currentAngles, self->s.apos.trBase ); VectorScale( setAngle,- 5, self->s.apos.trDelta ); self->s.apos.trTime = level.time; self->s.apos.trType = TR_LINEAR; } if ( diffPitch ) { if ( fabs(diffPitch) > maxPitchSpeed ) { // cap max speed self->speed += (diffPitch > 0.0f) ? -maxPitchSpeed : maxPitchSpeed; } else { // small enough, so just add half the diff so we smooth out the stopping self->speed -= ( diffPitch );//desiredAngles[PITCH]; } // Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit? if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) { if ( self->spawnflags & 2 ) { VectorSet( desiredAngles, 0.0f, 0.0f, -self->speed ); } else { VectorSet( desiredAngles, 0.0f, 0.0f, self->speed ); } G2Tur_SetBoneAngles(self, "pitch", desiredAngles); } else { if ( self->spawnflags & 2 ) { VectorSet( desiredAngles, self->speed, 0.0f, 0.0f ); } else { VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f ); } G2Tur_SetBoneAngles(self, "Bone_body", desiredAngles); } /* trap_G2API_SetBoneAngles( self->ghoul2, 0, "Bone_body", desiredAngles, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, level.time ); */ } if ( diffYaw || diffPitch ) { //FIXME: turbolaser sounds if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) { self->s.loopSound = G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" ); } else { self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" ); } } else { self->s.loopSound = 0; } }
void GCam_Update( void ) { int i; qboolean checkFollow = qfalse; qboolean checkTrack = qfalse; //Check for roffing angles if ( (client_camera.info_state & CAMERA_ROFFING) && !(client_camera.info_state & CAMERA_FOLLOWING) ) { if (client_camera.info_state & CAMERA_CUT) { // we're doing a cut, so just go to the new angles. none of this hifalutin lerping business. for ( i = 0; i < 3; i++ ) { cameraang[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); } } else { for ( i = 0; i < 3; i++ ) { cameraang[i] = client_camera.angles[i] + ( client_camera.angles2[i] / client_camera.pan_duration ) * ( level.time - client_camera.pan_time ); } } } else if ( client_camera.info_state & CAMERA_PANNING ) { if (client_camera.info_state & CAMERA_CUT) { // we're doing a cut, so just go to the new angles. none of this hifalutin lerping business. for ( i = 0; i < 3; i++ ) { cameraang[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); } } else { //Note: does not actually change the camera's angles until the pan time is done! if ( client_camera.pan_time + client_camera.pan_duration < level.time ) {//finished panning for ( i = 0; i < 3; i++ ) { cameraang[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); } client_camera.info_state &= ~CAMERA_PANNING; VectorCopy(client_camera.angles, cameraang ); } else {//still panning for ( i = 0; i < 3; i++ ) { //NOTE: does not store the resultant angle in client_camera.angles until pan is done cameraang[i] = client_camera.angles[i] + ( client_camera.angles2[i] / client_camera.pan_duration ) * ( level.time - client_camera.pan_time ); } } } } else { checkFollow = qtrue; } //Check for movement if ( client_camera.info_state & CAMERA_MOVING ) { //NOTE: does not actually move the camera until the movement time is done! if ( client_camera.move_time + client_camera.move_duration < level.time ) { VectorCopy( client_camera.origin2, client_camera.origin ); client_camera.info_state &= ~CAMERA_MOVING; VectorCopy( client_camera.origin, camerapos ); } else { if (client_camera.info_state & CAMERA_CUT) { // we're doing a cut, so just go to the new origin. none of this fancypants lerping stuff. for ( i = 0; i < 3; i++ ) { camerapos[i] = client_camera.origin2[i]; } } else { for ( i = 0; i < 3; i++ ) { camerapos[i] = client_camera.origin[i] + (( ( client_camera.origin2[i] - client_camera.origin[i] ) ) / client_camera.move_duration ) * ( level.time - client_camera.move_time ); } } } } else { checkTrack = qtrue; } if ( checkFollow ) { if ( client_camera.info_state & CAMERA_FOLLOWING ) {//This needs to be done after camera movement GCam_FollowUpdate(); } VectorCopy(client_camera.angles, cameraang ); } }
qboolean NPC_GetMoveDirection( vec3_t out, float *distance ) { vec3_t angles; //Clear the struct memset( &frameNavInfo, 0, sizeof( frameNavInfo ) ); //Get our movement, if any if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse ) return qfalse; //Setup the return value *distance = frameNavInfo.distance; //For starters VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection ); //If on a ladder, move appropriately if ( NPC->watertype & CONTENTS_LADDER ) { NPC_LadderMove( frameNavInfo.direction ); return qtrue; } //Attempt a straight move to goal if ( NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse ) { //See if we're just stuck if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) { //Can't reach goal, just face vectoangles( frameNavInfo.direction, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); VectorCopy( frameNavInfo.direction, out ); *distance = frameNavInfo.distance; return qfalse; } frameNavInfo.flags |= NIF_MACRO_NAV; } //Avoid any collisions on the way if ( NAV_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo ) == qfalse ) { //FIXME: Emit a warning, this is a worst case scenario //FIXME: if we have a clear path to our goal (exluding bodies), but then this // check (against bodies only) fails, shouldn't we fall back // to macro navigation? Like so: if ( !(frameNavInfo.flags&NIF_MACRO_NAV) ) {//we had a clear path to goal and didn't try macro nav, but can't avoid collision so try macro nav here //See if we're just stuck if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) { //Can't reach goal, just face vectoangles( frameNavInfo.direction, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); VectorCopy( frameNavInfo.direction, out ); *distance = frameNavInfo.distance; return qfalse; } frameNavInfo.flags |= NIF_MACRO_NAV; } } //Setup the return values VectorCopy( frameNavInfo.direction, out ); *distance = frameNavInfo.distance; return qtrue; }
qboolean NPC_GetMoveDirectionAltRoute( vec3_t out, float *distance, qboolean tryStraight ) { vec3_t angles; NPCInfo->aiFlags &= ~NPCAI_BLOCKED; //Clear the struct memset( &frameNavInfo, 0, sizeof( frameNavInfo ) ); //Get our movement, if any if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse ) return qfalse; //Setup the return value *distance = frameNavInfo.distance; //For starters VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection ); //If on a ladder, move appropriately if ( NPC->watertype & CONTENTS_LADDER ) { NPC_LadderMove( frameNavInfo.direction ); return qtrue; } //Attempt a straight move to goal if ( !tryStraight || NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse ) {//blocked //Can't get straight to goal, use macro nav if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) { //Can't reach goal, just face vectoangles( frameNavInfo.direction, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); VectorCopy( frameNavInfo.direction, out ); *distance = frameNavInfo.distance; return qfalse; } //else we are on our way frameNavInfo.flags |= NIF_MACRO_NAV; } else {//we have no architectural problems, see if there are ents inthe way and try to go around them //not blocked if ( d_altRoutes.integer ) {//try macro nav navInfo_t tempInfo; memcpy( &tempInfo, &frameNavInfo, sizeof( tempInfo ) ); if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &tempInfo, qtrue, 5 ) == qfalse ) {//revert to macro nav //Can't get straight to goal, dump tempInfo and use macro nav if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) { //Can't reach goal, just face vectoangles( frameNavInfo.direction, angles ); NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); VectorCopy( frameNavInfo.direction, out ); *distance = frameNavInfo.distance; return qfalse; } //else we are on our way frameNavInfo.flags |= NIF_MACRO_NAV; } else {//otherwise, either clear or can avoid memcpy( &frameNavInfo, &tempInfo, sizeof( frameNavInfo ) ); } } else {//OR: just give up if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo, qtrue, 30 ) == qfalse ) {//give up return qfalse; } } } //Setup the return values VectorCopy( frameNavInfo.direction, out ); *distance = frameNavInfo.distance; return qtrue; }
/* ------------------------- Droid_Patrol ------------------------- */ void Droid_Patrol( void ) { NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); if ( NPC->client && NPC->client->NPC_class != CLASS_GONK ) { if (NPC->client->NPC_class != CLASS_R5D2) { //he doesn't have an eye. R2D2_PartsMove(); // Get his eye moving. } R2D2_TurnAnims(); } //If we have somewhere to go, then do that if ( UpdateGoal() ) { ucmd.buttons |= BUTTON_WALKING; NPC_MoveToGoal( qtrue ); if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE ) { NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 ) { if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 ) { if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } if( NPC->client && NPC->client->NPC_class == CLASS_GONK ) { if (TIMER_Done(NPC,"patrolNoise")) { G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) ); TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); } } // else // { // R5D2_LookAround(); // } } NPC_UpdateAngles( qtrue, qtrue ); }
static float CalcDamageModifier( vec3_t point, gentity_t *target, class_t pcl, int damageFlags ) { vec3_t targOrigin, bulletPath, bulletAngle, pMINUSfloor, floor, normal; float clientHeight, hitRelative, hitRatio, modifier; int hitRotation; // handle nonlocational damage if ( damageFlags & DAMAGE_NO_LOCDAMAGE ) { return G_GetNonLocDamageMod( pcl ); } // need a valid point for point damage if ( point == NULL ) { return 1.0f; } // Get the point location relative to the floor under the target if ( g_unlagged.integer && target->client && target->client->unlaggedCalc.used ) { VectorCopy( target->client->unlaggedCalc.origin, targOrigin ); } else { VectorCopy( target->r.currentOrigin, targOrigin ); } BG_GetClientNormal( &target->client->ps, normal ); VectorMA( targOrigin, target->r.mins[ 2 ], normal, floor ); VectorSubtract( point, floor, pMINUSfloor ); // Get the proportion of the target height where the hit landed clientHeight = target->r.maxs[ 2 ] - target->r.mins[ 2 ]; if ( !clientHeight ) { clientHeight = 1.0f; } hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal ); if ( hitRelative < 0.0f ) { hitRelative = 0.0f; } if ( hitRelative > clientHeight ) { hitRelative = clientHeight; } hitRatio = hitRelative / clientHeight; // Get the yaw of the attack relative to the target's view yaw VectorSubtract( point, targOrigin, bulletPath ); vectoangles( bulletPath, bulletAngle ); hitRotation = AngleNormalize360( target->client->ps.viewangles[ YAW ] - bulletAngle[ YAW ] ); // Get damage region modifier modifier = G_GetPointDamageMod( target, pcl, hitRotation, hitRatio ); return modifier; }
void CGCam_Update( void ) { int i; qboolean checkFollow = qfalse; qboolean checkTrack = qfalse; // Apply new roff data to the camera as needed if ( client_camera.info_state & CAMERA_ROFFING ) { CGCam_Roff(); } //Check for a zoom if (client_camera.info_state & CAMERA_ACCEL) { // x = x0 + vt + 0.5*a*t*t float actualFOV_X = client_camera.FOV; float sanityMin = 1, sanityMax = 180; float t = (cg.time - client_camera.FOV_time)*0.001; // mult by 0.001 cuz otherwise t is too darned big float fovDuration = client_camera.FOV_duration; #ifndef FINAL_BUILD if (cg_roffval4.integer) { fovDuration = cg_roffval4.integer; } #endif if ( client_camera.FOV_time + fovDuration < cg.time ) { client_camera.info_state &= ~CAMERA_ACCEL; } else { float initialPosVal = client_camera.FOV2; float velVal = client_camera.FOV_vel; float accVal = client_camera.FOV_acc; #ifndef FINAL_BUILD if (cg_roffdebug.integer) { if (fabs(cg_roffval1.value) > 0.001f) { initialPosVal = cg_roffval1.value; } if (fabs(cg_roffval2.value) > 0.001f) { velVal = cg_roffval2.value; } if (fabs(cg_roffval3.value) > 0.001f) { accVal = cg_roffval3.value; } } #endif float initialPos = initialPosVal; float vel = velVal*t; float acc = 0.5*accVal*t*t; actualFOV_X = initialPos + vel + acc; if (cg_roffdebug.integer) { Com_Printf("%d: fovaccel from %2.1f using vel = %2.4f, acc = %2.4f (current fov calc = %5.6f)\n", cg.time, initialPosVal, velVal, accVal, actualFOV_X); } if (actualFOV_X < sanityMin) { actualFOV_X = sanityMin; } else if (actualFOV_X > sanityMax) { actualFOV_X = sanityMax; } client_camera.FOV = actualFOV_X; } CG_CalcFOVFromX( actualFOV_X ); } else if ( client_camera.info_state & CAMERA_ZOOMING ) { float actualFOV_X; if ( client_camera.FOV_time + client_camera.FOV_duration < cg.time ) { actualFOV_X = client_camera.FOV = client_camera.FOV2; client_camera.info_state &= ~CAMERA_ZOOMING; } else { actualFOV_X = client_camera.FOV + (( ( client_camera.FOV2 - client_camera.FOV ) ) / client_camera.FOV_duration ) * ( cg.time - client_camera.FOV_time ); } CG_CalcFOVFromX( actualFOV_X ); } else { CG_CalcFOVFromX( client_camera.FOV ); } //Check for roffing angles if ( (client_camera.info_state & CAMERA_ROFFING) && !(client_camera.info_state & CAMERA_FOLLOWING) ) { if (client_camera.info_state & CAMERA_CUT) { // we're doing a cut, so just go to the new angles. none of this hifalutin lerping business. for ( i = 0; i < 3; i++ ) { cg.refdefViewAngles[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); } } else { for ( i = 0; i < 3; i++ ) { cg.refdefViewAngles[i] = client_camera.angles[i] + ( client_camera.angles2[i] / client_camera.pan_duration ) * ( cg.time - client_camera.pan_time ); } } } else if ( client_camera.info_state & CAMERA_PANNING ) { if (client_camera.info_state & CAMERA_CUT) { // we're doing a cut, so just go to the new angles. none of this hifalutin lerping business. for ( i = 0; i < 3; i++ ) { cg.refdefViewAngles[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); } } else { //Note: does not actually change the camera's angles until the pan time is done! if ( client_camera.pan_time + client_camera.pan_duration < cg.time ) {//finished panning for ( i = 0; i < 3; i++ ) { client_camera.angles[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); } client_camera.info_state &= ~CAMERA_PANNING; VectorCopy(client_camera.angles, cg.refdefViewAngles ); } else {//still panning for ( i = 0; i < 3; i++ ) { //NOTE: does not store the resultant angle in client_camera.angles until pan is done cg.refdefViewAngles[i] = client_camera.angles[i] + ( client_camera.angles2[i] / client_camera.pan_duration ) * ( cg.time - client_camera.pan_time ); } } } } else { checkFollow = qtrue; } //Check for movement if ( client_camera.info_state & CAMERA_MOVING ) { //NOTE: does not actually move the camera until the movement time is done! if ( client_camera.move_time + client_camera.move_duration < cg.time ) { VectorCopy( client_camera.origin2, client_camera.origin ); client_camera.info_state &= ~CAMERA_MOVING; VectorCopy( client_camera.origin, cg.refdef.vieworg ); } else { if (client_camera.info_state & CAMERA_CUT) { // we're doing a cut, so just go to the new origin. none of this fancypants lerping stuff. for ( i = 0; i < 3; i++ ) { cg.refdef.vieworg[i] = client_camera.origin2[i]; } } else { for ( i = 0; i < 3; i++ ) { cg.refdef.vieworg[i] = client_camera.origin[i] + (( ( client_camera.origin2[i] - client_camera.origin[i] ) ) / client_camera.move_duration ) * ( cg.time - client_camera.move_time ); } } } } else { checkTrack = qtrue; } if ( checkFollow ) { if ( client_camera.info_state & CAMERA_FOLLOWING ) {//This needs to be done after camera movement CGCam_FollowUpdate(); } VectorCopy(client_camera.angles, cg.refdefViewAngles ); } if ( checkTrack ) { if ( client_camera.info_state & CAMERA_TRACKING ) {//This has to run AFTER Follow if the camera is following a cameraGroup CGCam_TrackUpdate(); } VectorCopy( client_camera.origin, cg.refdef.vieworg ); } //Bar fading if ( client_camera.info_state & CAMERA_BAR_FADING ) { CGCam_UpdateBarFade(); } //Normal fading - separate call because can finish after camera is disabled CGCam_UpdateFade(); //Update shaking if there's any //CGCam_UpdateSmooth( cg.refdef.vieworg, cg.refdefViewAngles ); CGCam_UpdateShake( cg.refdef.vieworg, cg.refdefViewAngles ); AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); }
/* =============== G_CallSpawnFunction Finds the spawn function for the entity and calls it, returning qfalse if not found =============== */ qboolean G_CallSpawnFunction( gentity_t *spawnedEntity ) { entityClassDescriptor_t *spawnedClass; buildable_t buildable; if ( !spawnedEntity->classname ) { //don't even warn about spawning-errors with -2 (maps might still work at least partly if we ignore these willingly) if ( g_debugEntities.integer > -2 ) G_Printf( S_ERROR "Entity " S_COLOR_CYAN "#%i" S_COLOR_WHITE " is missing classname – we are unable to spawn it.\n", spawnedEntity->s.number ); return qfalse; } //check buildable spawn functions buildable = BG_BuildableByEntityName( spawnedEntity->classname )->number; if ( buildable != BA_NONE ) { // don't spawn built-in buildings if we are using a custom layout if ( level.layout[ 0 ] && Q_stricmp( level.layout, S_BUILTIN_LAYOUT ) ) { return qfalse; } if ( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN ) { spawnedEntity->s.angles[ YAW ] += 180.0f; AngleNormalize360( spawnedEntity->s.angles[ YAW ] ); } G_SpawnBuildable( spawnedEntity, buildable ); return qtrue; } // check the spawn functions for other classes spawnedClass = bsearch( spawnedEntity->classname, entityClassDescriptions, ARRAY_LEN( entityClassDescriptions ), sizeof( entityClassDescriptor_t ), cmdcmp ); if ( spawnedClass ) { // found it spawnedEntity->eclass = &entityClasses[(int) (spawnedClass-entityClassDescriptions)]; spawnedEntity->eclass->instanceCounter++; if(!G_ValidateEntity( spawnedClass, spawnedEntity )) return qfalse; // results in freeing the entity spawnedClass->spawn( spawnedEntity ); spawnedEntity->spawned = qtrue; if ( g_debugEntities.integer > 2 ) G_Printf( S_DEBUG "Successfully spawned entity " S_COLOR_CYAN "#%i" S_COLOR_WHITE " as " S_COLOR_YELLOW "%i" S_COLOR_WHITE "th instance of " S_COLOR_CYAN "%s\n", spawnedEntity->s.number, spawnedEntity->eclass->instanceCounter, spawnedClass->name); /* * to allow each spawn function to test and handle for itself, * we handle it automatically *after* the spawn (but before it's use/reset) */ if(!G_HandleEntityVersions( spawnedClass, spawnedEntity )) return qfalse; return qtrue; } //don't even warn about spawning-errors with -2 (maps might still work at least partly if we ignore these willingly) if ( g_debugEntities.integer > -2 ) { if (!Q_stricmp(S_WORLDSPAWN, spawnedEntity->classname)) { G_Printf( S_ERROR "a " S_COLOR_CYAN S_WORLDSPAWN S_COLOR_WHITE " class was misplaced into position " S_COLOR_CYAN "#%i" S_COLOR_WHITE " of the spawn string – Ignoring\n", spawnedEntity->s.number ); } else { G_Printf( S_ERROR "Unknown entity class \"" S_COLOR_CYAN "%s" S_COLOR_WHITE "\".\n", spawnedEntity->classname ); } } return qfalse; }
void CGCam_Pan( vec3_t dest, vec3_t panDirection, float duration ) { //vec3_t panDirection = {0, 0, 0}; int i; float delta1 , delta2; CGCam_FollowDisable(); CGCam_DistanceDisable(); if ( !duration ) { CGCam_SetAngles( dest ); client_camera.info_state &= ~CAMERA_PANNING; return; } //FIXME: make the dest an absolute value, and pass in a //panDirection as well. If a panDirection's axis value is //zero, find the shortest difference for that axis. //Store the delta in client_camera.angles2. for( i = 0; i < 3; i++ ) { dest[i] = AngleNormalize360( dest[i] ); delta1 = dest[i] - AngleNormalize360( client_camera.angles[i] ); if ( delta1 < 0 ) { delta2 = delta1 + 360; } else { delta2 = delta1 - 360; } if ( !panDirection[i] ) {//Didn't specify a direction, pick shortest if( Q_fabs(delta1) < Q_fabs(delta2) ) { client_camera.angles2[i] = delta1; } else { client_camera.angles2[i] = delta2; } } else if ( panDirection[i] < 0 ) { if( delta1 < 0 ) { client_camera.angles2[i] = delta1; } else if( delta1 > 0 ) { client_camera.angles2[i] = delta2; } else {//exact client_camera.angles2[i] = 0; } } else if ( panDirection[i] > 0 ) { if( delta1 > 0 ) { client_camera.angles2[i] = delta1; } else if( delta1 < 0 ) { client_camera.angles2[i] = delta2; } else {//exact client_camera.angles2[i] = 0; } } } //VectorCopy( dest, client_camera.angles2 ); client_camera.info_state |= CAMERA_PANNING; client_camera.pan_duration = duration; client_camera.pan_time = cg.time; }
void CL_FinishMove( usercmd_t *cmd ) { int i; // copy the state that the cgame is currently sending cmd->weapon = cl.cgameUserCmdValue; cmd->forcesel = cl.cgameForceSelection; cmd->invensel = cl.cgameInvenSelection; if (cl.gcmdSendValue) { cmd->generic_cmd = cl.gcmdValue; //cl.gcmdSendValue = qfalse; cl.gcmdSentValue = qtrue; } else { cmd->generic_cmd = 0; } // send the current server time so the amount of movement // can be determined without allowing cheating cmd->serverTime = cl.serverTime; if (cl.cgameViewAngleForceTime > cl.serverTime) { cl.cgameViewAngleForce[YAW] -= SHORT2ANGLE(cl.snap.ps.delta_angles[YAW]); cl.viewangles[YAW] = cl.cgameViewAngleForce[YAW]; cl.cgameViewAngleForceTime = 0; } if ( cl_crazyShipControls ) { float pitchSubtract, pitchDelta, yawDelta; yawDelta = AngleSubtract(cl.viewangles[YAW],cl_lastViewAngles[YAW]); //yawDelta *= (4.0f*pVeh->m_fTimeModifier); cl_sendAngles[ROLL] -= yawDelta; float nRoll = fabs(cl_sendAngles[ROLL]); pitchDelta = AngleSubtract(cl.viewangles[PITCH],cl_lastViewAngles[PITCH]); //pitchDelta *= (2.0f*pVeh->m_fTimeModifier); pitchSubtract = pitchDelta * (nRoll/90.0f); cl_sendAngles[PITCH] += pitchDelta-pitchSubtract; //yaw-roll calc should be different if (nRoll > 90.0f) { nRoll -= 180.0f; } if (nRoll < 0.0f) { nRoll = -nRoll; } pitchSubtract = pitchDelta * (nRoll/90.0f); if ( cl_sendAngles[ROLL] > 0.0f ) { cl_sendAngles[YAW] += pitchSubtract; } else { cl_sendAngles[YAW] -= pitchSubtract; } cl_sendAngles[PITCH] = AngleNormalize180( cl_sendAngles[PITCH] ); cl_sendAngles[YAW] = AngleNormalize360( cl_sendAngles[YAW] ); cl_sendAngles[ROLL] = AngleNormalize180( cl_sendAngles[ROLL] ); for (i=0 ; i<3 ; i++) { cmd->angles[i] = ANGLE2SHORT(cl_sendAngles[i]); } } else { for (i=0 ; i<3 ; i++) { cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); } //in case we switch to the cl_crazyShipControls VectorCopy( cl.viewangles, cl_sendAngles ); } //always needed in for the cl_crazyShipControls VectorCopy( cl.viewangles, cl_lastViewAngles ); }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn and evolve Initializes all non-persistent parts of playerState ============ */ void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; bool savedNoclip, savedCliprcontents; int persistant[ MAX_PERSISTANT ]; gentity_t *spawnPoint = nullptr; int flags; int savedPing; int teamLocal; int eventSequence; char userinfo[ MAX_INFO_STRING ]; vec3_t up = { 0.0f, 0.0f, 1.0f }; int maxAmmo, maxClips; weapon_t weapon; ClientSpawnCBSE(ent, ent == spawn); index = ent - g_entities; client = ent->client; teamLocal = client->pers.team; //if client is dead and following teammate, stop following before spawning if ( client->sess.spectatorClient != -1 ) { client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } // only start client if chosen a class and joined a team if ( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) { client->sess.spectatorState = SPECTATOR_FREE; } else if ( client->pers.classSelection == PCL_NONE ) { client->sess.spectatorState = SPECTATOR_LOCKED; } // if client is dead and following teammate, stop following before spawning if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { G_StopFollowing( ent ); } if ( origin != nullptr ) { VectorCopy( origin, spawn_origin ); } if ( angles != nullptr ) { VectorCopy( angles, spawn_angles ); } // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if ( client->sess.spectatorState != SPECTATOR_NOT ) { if ( teamLocal == TEAM_NONE ) { spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); } else if ( teamLocal == TEAM_ALIENS ) { spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); } else if ( teamLocal == TEAM_HUMANS ) { spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); } } else { if ( spawn == nullptr ) { Com_Error(errorParm_t::ERR_DROP, "ClientSpawn: spawn is NULL" ); } spawnPoint = spawn; if ( spawnPoint->s.eType == entityType_t::ET_BUILDABLE ) { G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, true ); if ( spawnPoint->buildableTeam == TEAM_ALIENS ) { spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; } else if ( spawnPoint->buildableTeam == TEAM_HUMANS ) { spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } } // toggle the teleport bit so the client knows to not lerp flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistent data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; savedNoclip = client->noclip; savedCliprcontents = client->cliprcontents; for ( i = 0; i < MAX_PERSISTANT; i++ ) { persistant[ i ] = client->ps.persistant[ i ]; } eventSequence = client->ps.eventSequence; memset( client, 0, sizeof( *client ) ); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->noclip = savedNoclip; client->cliprcontents = savedCliprcontents; for ( i = 0; i < MAX_PERSISTANT; i++ ) { client->ps.persistant[ i ] = persistant[ i ]; } client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); client->ps.eFlags = flags; //Log::Notice( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[ index ]; ent->classname = S_PLAYER_CLASSNAME; if ( client->noclip ) { client->cliprcontents = CONTENTS_BODY; } else { ent->r.contents = CONTENTS_BODY; } ent->clipmask = MASK_PLAYERSOLID; ent->die = G_PlayerDie; ent->waterlevel = 0; ent->watertype = 0; ent->flags &= FL_GODMODE | FL_NOTARGET; // calculate each client's acceleration ent->evaluateAcceleration = true; client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, nullptr, nullptr, nullptr ); // clear entity values if ( ent->client->pers.classSelection == PCL_HUMAN_NAKED ) { BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } else if ( client->sess.spectatorState == SPECTATOR_NOT ) { weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; } else { weapon = WP_NONE; } maxAmmo = BG_Weapon( weapon )->maxAmmo; maxClips = BG_Weapon( weapon )->maxClips; client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; // We just spawned, not changing weapons client->ps.persistant[ PERS_NEWWEAPON ] = 0; client->ps.persistant[ PERS_TEAM ] = client->pers.team; // TODO: Check whether stats can be cleared at once instead of per field client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_MAX; client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; client->ps.stats[ STAT_PREDICTION ] = 0; client->ps.stats[ STAT_STATE ] = 0; VectorSet( client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); //clear the credits array // TODO: Handle in HealthComponent or ClientComponent. for ( i = 0; i < MAX_CLIENTS; i++ ) { ent->credits[ i ].value = 0.0f; ent->credits[ i ].time = 0; ent->credits[ i ].team = TEAM_NONE; } G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); //give aliens some spawn velocity if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_ALIENS ) { if ( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); } else { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); if ( spawnPoint->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, nullptr, nullptr ); VectorAdd( spawnPoint->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, BG_Class( ent->client->pers.classSelection )->jumpMagnitude, client->ps.velocity ); } G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } else if ( client->sess.spectatorState == SPECTATOR_NOT && client->pers.team == TEAM_HUMANS ) { spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); } // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); if ( client->sess.spectatorState == SPECTATOR_NOT ) { trap_LinkEntity( ent ); // force the base weapon up if ( client->pers.team == TEAM_HUMANS ) { G_ForceWeaponChange( ent, weapon ); } client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; usercmdClearButtons( client->latched_buttons ); // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if ( level.intermissiontime ) { MoveClientToIntermission( ent ); } else { // fire the targets of the spawn point if ( !spawn && spawnPoint ) { G_EventFireEntity( spawnPoint, ent, ON_SPAWN ); } // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for ( i = WP_NUM_WEAPONS - 1; i > 0; i-- ) { if ( BG_InventoryContainsWeapon( i, client->ps.stats ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent - g_entities ); // positively link the client, even if the command times are weird if ( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, true ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // must do this here so the number of active clients is calculated CalculateRanks(); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, true ); client->pers.infoChangeTime = level.time; // (re)tag the client for its team Beacon::DeleteTags( ent ); Beacon::Tag( ent, (team_t)ent->client->ps.persistant[ PERS_TEAM ], true ); }
/************************************************************************************************ * CRMBSPInstance::Spawn * spawns a bsp into the world using the previously aquired origin * * inputs: * none * * return: * none * ************************************************************************************************/ bool CRMBSPInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer) { #ifndef PRE_RELEASE_DEMO // TEntity* ent; float yaw; char temp[10000]; char *savePtr; vec3_t origin; vec3_t notmirrored; float water_level = terrain->GetLandScape()->GetWaterHeight(); const vec3_t& terxelSize = terrain->GetLandScape()->GetTerxelSize ( ); const vec3pair_t& bounds = terrain->GetLandScape()->GetBounds(); // If this entity somehow lost its collision flag then boot it if ( !GetArea().IsCollisionEnabled ( ) ) { return false; } // copy out the unmirrored version VectorCopy(GetOrigin(), notmirrored); // we want to mirror it before determining the Z value just in case the landscape isn't perfectly mirrored if (mMirror) { GetOrigin()[0] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][0] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][0] - GetOrigin()[0]; GetOrigin()[1] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][1] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][1] - GetOrigin()[1]; } // Align the instance to the center of a terxel GetOrigin ( )[0] = bounds[0][0] + (int)((GetOrigin ( )[0] - bounds[0][0] + terxelSize[0] / 2) / terxelSize[0]) * terxelSize[0]; GetOrigin ( )[1] = bounds[0][1] + (int)((GetOrigin ( )[1] - bounds[0][1] + terxelSize[1] / 2) / terxelSize[1]) * terxelSize[1]; // Make sure the bsp is resting on the ground, not below or above it // NOTE: This check is basically saying "is this instance not a bridge", because when instances are created they are all // placed above the world's Z boundary, EXCEPT FOR BRIDGES. So this call to GetWorldHeight will move all other instances down to // ground level except bridges if ( GetOrigin()[2] > terrain->GetBounds()[1][2] ) { if( GetFlattenRadius() ) { terrain->GetLandScape()->GetWorldHeight ( GetOrigin(), GetBounds ( ), false ); GetOrigin()[2] += 5; } else if (IsServer) { // if this instance does not flatten the ground around it, do a trace to more accurately determine its Z value trace_t tr; vec3_t end; vec3_t start; VectorCopy(GetOrigin(), end); VectorCopy(GetOrigin(), start); // start the trace below the top height of the landscape start[2] = TheRandomMissionManager->GetLandScape()->GetBounds()[1][2] - 1; // end the trace at the bottom of the world end[2] = MIN_WORLD_COORD; memset ( &tr, 0, sizeof ( tr ) ); SV_Trace( &tr, start, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_TERRAIN|CONTENTS_SOLID, G2_NOCOLLIDE, 0); //qfalse, 0, 10 ); if( !(tr.contents & CONTENTS_TERRAIN) || (tr.fraction == 1.0) ) { if ( 0 ) assert(0); // this should never happen // restore the unmirrored origin VectorCopy( notmirrored, GetOrigin() ); // don't spawn return false; } // assign the Z-value to wherever it hit the terrain GetOrigin()[2] = tr.endpos[2]; // lower it a little, otherwise the bottom of the instance might be exposed if on some weird sloped terrain GetOrigin()[2] -= 16; // FIXME: would it be better to use a number related to the instance itself like 1/5 it's height or something... } } else { terrain->GetLandScape()->GetWorldHeight ( GetOrigin(), GetBounds ( ), true ); } // save away the origin VectorCopy(GetOrigin(), origin); // make sure not to spawn if in water if (!HasObjective() && GetOrigin()[2] < water_level) return false; // restore the origin VectorCopy(origin, GetOrigin()); if (mMirror) { // change blue things to red for symmetric maps if (strlen(mFilter) > 0) { char * blue = strstr(mFilter,"blue"); if (blue) { blue[0] = (char) 0; strcat(mFilter, "red"); SetSide(SIDE_RED); } } if (strlen(mTeamFilter) > 0) { char * blue = strstr(mTeamFilter,"blue"); if (blue) { strcpy(mTeamFilter, "red"); SetSide(SIDE_RED); } } yaw = RAD2DEG(mArea->GetAngle() + mBaseAngle) + 180; } else { yaw = RAD2DEG(mArea->GetAngle() + mBaseAngle); } /* if( TheRandomMissionManager->GetMission()->GetSymmetric() ) { vec3_t diagonal; vec3_t lineToPoint; vec3_t mins; vec3_t maxs; vec3_t point; vec3_t vProj; vec3_t vec; float distance; VectorCopy( TheRandomMissionManager->GetLandScape()->GetBounds()[1], maxs ); VectorCopy( TheRandomMissionManager->GetLandScape()->GetBounds()[0], mins ); VectorCopy( GetOrigin(), point ); mins[2] = maxs[2] = point[2] = 0; VectorSubtract( point, mins, lineToPoint ); VectorSubtract( maxs, mins, diagonal); VectorNormalize(diagonal); VectorMA( mins, DotProduct(lineToPoint, diagonal), diagonal, vProj); VectorSubtract(point, vProj, vec ); distance = VectorLength(vec); // if an instance is too close to the imaginary diagonal that cuts the world in half, don't spawn it // otherwise you can get overlapping instances if( distance < GetSpacingRadius() ) { #ifdef _DEBUG mAutomapSymbol = AUTOMAP_END; #endif if( !HasObjective() ) { return false; } } } */ // Spawn in the bsp model sprintf(temp, "{\n" "\"classname\" \"misc_bsp\"\n" "\"bspmodel\" \"%s\"\n" "\"origin\" \"%f %f %f\"\n" "\"angles\" \"0 %f 0\"\n" "\"filter\" \"%s\"\n" "\"teamfilter\" \"%s\"\n" "\"spacing\" \"%d\"\n" "\"flatten\" \"%d\"\n" "}\n", mBsp, GetOrigin()[0], GetOrigin()[1], GetOrigin()[2], AngleNormalize360(yaw), mFilter, mTeamFilter, (int)GetSpacingRadius(), (int)GetFlattenRadius() ); if (IsServer) { // only allow for true spawning on the server savePtr = sv.entityParsePoint; sv.entityParsePoint = temp; // VM_Call( cgvm, GAME_SPAWN_RMG_ENTITY ); // char *s; int bufferSize = 1024; char buffer[1024]; // s = COM_Parse( (const char **)&sv.entityParsePoint ); Q_strncpyz( buffer, sv.entityParsePoint, bufferSize ); if ( sv.entityParsePoint && sv.entityParsePoint[0] ) { ge->GameSpawnRMGEntity(sv.entityParsePoint); } sv.entityParsePoint = savePtr; } #ifndef DEDICATED DrawAutomapSymbol(); #endif Com_DPrintf( "RMG: Building '%s' spawned at (%f %f %f)\n", mBsp, GetOrigin()[0], GetOrigin()[1], GetOrigin()[2] ); // now restore the instances un-mirrored origin // NOTE: all this origin flipping, setting the side etc... should be done when mMirror is set // because right after this function is called, mMirror is set to 0 but all the instance data is STILL MIRRORED -- not good VectorCopy(notmirrored, GetOrigin()); #endif // PRE_RELEASE_DEMO return true; }
// Make the change in angles a little more gradual, not so snappy // Subtle, but noticeable. // // Modified from the original id ChangeYaw code... void ACEMV_ChangeBotAngle(gentity_t * ent) { #if 1 vec3_t ideal_angles; float ideal_yaw; float ideal_pitch; float current_yaw; float current_pitch; float move; float speed; // Normalize the move angle first VectorNormalize(ent->bs.moveVector); current_yaw = AngleNormalize360(ent->bs.viewAngles[YAW]); current_pitch = AngleNormalize360(ent->bs.viewAngles[PITCH]); VectorToAngles(ent->bs.moveVector, ideal_angles); ideal_yaw = AngleNormalize360(ideal_angles[YAW]); ideal_pitch = AngleNormalize360(ideal_angles[PITCH]); // yaw if(current_yaw != ideal_yaw) { move = ideal_yaw - current_yaw; speed = ent->bs.turnSpeed; if(ideal_yaw > current_yaw) { if(move >= 180) move = move - 360; } else { if(move <= -180) move = move + 360; } if(move > 0) { if(move > speed) move = speed; } else { if(move < -speed) move = -speed; } ent->bs.viewAngles[YAW] = AngleNormalize360(current_yaw + move); } // pitch if(current_pitch != ideal_pitch) { move = ideal_pitch - current_pitch; speed = ent->bs.turnSpeed; if(ideal_pitch > current_pitch) { if(move >= 180) move = move - 360; } else { if(move <= -180) move = move + 360; } if(move > 0) { if(move > speed) move = speed; } else { if(move < -speed) move = -speed; } ent->bs.viewAngles[PITCH] = AngleNormalize360(current_pitch + move); } #else #endif }
void NPC_BSWander (void) {//FIXME: don't actually go all the way to the next waypoint, just move in fits and jerks...? if ( !NPCInfo->investigateDebounceTime ) {//Starting out float minGoalReachedDistSquared = 64;//32*32; vec3_t vec; //Keep moving toward our tempGoal NPCInfo->goalEntity = NPCInfo->tempGoal; VectorSubtract ( NPCInfo->tempGoal->currentOrigin, NPC->currentOrigin, vec); if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) { minGoalReachedDistSquared = 64; } if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) { //Close enough, just got there NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); if( !Q_irand(0, 1) ) { NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); } else { NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); } //Just got here, so Look around for a while NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); } else { //Keep moving toward goal NPC_MoveToGoal( qtrue ); } } else { //We're there if ( NPCInfo->investigateDebounceTime > level.time ) { //Still waiting around for a bit //Turn angles every now and then to look around if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) { if ( !Q_irand( 0, 30 ) ) { int numEdges = navigator.GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); if ( numEdges != WAYPOINT_NONE ) { int branchNum = Q_irand( 0, numEdges - 1 ); vec3_t branchPos, lookDir; int nextWp = navigator.GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum ); navigator.GetNodePosition( nextWp, branchPos ); VectorSubtract( branchPos, NPCInfo->tempGoal->currentOrigin, lookDir ); NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) ); } } } } else {//Just finished waiting NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); if ( NPC->waypoint != WAYPOINT_NONE ) { int numEdges = navigator.GetNodeNumEdges( NPC->waypoint ); if ( numEdges != WAYPOINT_NONE ) { int branchNum = Q_irand( 0, numEdges - 1 ); int nextWp = navigator.GetNodeEdge( NPC->waypoint, branchNum ); navigator.GetNodePosition( nextWp, NPCInfo->tempGoal->currentOrigin ); NPCInfo->tempGoal->waypoint = nextWp; } NPCInfo->investigateDebounceTime = 0; //Start moving toward our tempGoal NPCInfo->goalEntity = NPCInfo->tempGoal; NPC_MoveToGoal( qtrue ); } } } NPC_UpdateAngles( qtrue, qtrue ); }
qboolean NPC_MoveToGoal( qboolean tryStraight ) { float distance; vec3_t dir; #if AI_TIMERS int startTime = GetTime(0); #endif// AI_TIMERS //If taking full body pain, don't move if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) { return qfalse; } #ifdef __DOMINANCE_NPC__ if (NPC->enemy && NPC->s.weapon == WP_SABER && (!NPC_EnemyVisible( NPC, NPC->enemy ) || (Distance(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin) > 96 || NPC->genericValue15 < level.time))) { // Enemy is visible, but out of range for lghtsaber... Move closer... NPC->client->ps.speed = NPCInfo->stats.runSpeed; if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } return qtrue; } else if (NPC->enemy && NPC_EnemyVisible( NPC, NPC->enemy )) { // Enemy is visible and in range, no need to move at the moment... return qfalse; } else if (NPC->enemy) {// Have an enemy that is not currently visible... if (NPC->s.weapon == WP_SABER && (Distance(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin) > 96 || NPC->genericValue15 < level.time)) { if (NPC->genericValue14 < level.time) { // Give up... NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->longTermGoal = -1; } } else if (NPC->enemy && NPC->genericValue15 < level.time) { if (NPC->genericValue14 < level.time) { // Give up... NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->longTermGoal = -1; } } NPC->client->ps.speed = NPCInfo->stats.runSpeed; if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } return qtrue; } else {// Dominance: Use bot waypointing AI if it is available! - Unique1 NPC->enemy = NULL; NPCInfo->goalEntity = NULL; NPC->client->ps.speed = NPCInfo->stats.walkSpeed; if (!NPC_FollowRoutes()) { //G_Printf("NPC_FollowRoutes failed!\n"); return qfalse; } return qtrue; } #endif //__DOMINANCE_NPC__ /* if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) {//If in an emplaced gun, never try to navigate! return qtrue; } */ //rwwFIXMEFIXME: emplaced support //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least? //Get our movement direction #if 1 if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse ) #else if ( NPC_GetMoveDirection( dir, &distance ) == qfalse ) #endif return qfalse; NPCInfo->distToGoal = distance; //Convert the move to angles vectoangles( dir, NPCInfo->lastPathAngles ); if ( (ucmd.buttons&BUTTON_WALKING) ) { NPC->client->ps.speed = NPCInfo->stats.walkSpeed; } else { NPC->client->ps.speed = NPCInfo->stats.runSpeed; } //FIXME: still getting ping-ponging in certain cases... !!! Nav/avoidance error? WTF???!!! //If in combat move, then move directly towards our goal if ( NPC_CheckCombatMove() ) {//keep current facing G_UcmdMoveForDir( NPC, &ucmd, dir ); } else {//face our goal //FIXME: strafe instead of turn if change in dir is small and temporary NPCInfo->desiredPitch = 0.0f; NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->lastPathAngles[YAW] ); //Pitch towards the goal and also update if flying or swimming if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) { NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] ); if ( dir[2] ) { float scale = (dir[2] * distance); if ( scale > 64 ) { scale = 64; } else if ( scale < -64 ) { scale = -64; } NPC->client->ps.velocity[2] = scale; //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64; } } //Set any final info ucmd.forwardmove = 127; } #if AI_TIMERS navTime += GetTime( startTime ); #endif// AI_TIMERS return qtrue; }
void NPC_BSSearch (void) { NPC_CheckEnemy(qtrue, qfalse); //Look for enemies, if find one: if ( NPC->enemy ) { if( NPCInfo->tempBehavior == BS_SEARCH ) {//if tempbehavior, set tempbehavior to default NPCInfo->tempBehavior = BS_DEFAULT; } else {//if bState, change to run and shoot NPCInfo->behaviorState = BS_HUNT_AND_KILL; NPC_BSRunAndShoot(); } return; } //FIXME: what if our goalEntity is not NULL and NOT our tempGoal - they must //want us to do something else? If tempBehavior, just default, else set //to run and shoot...? //FIXME: Reimplement if ( !NPCInfo->investigateDebounceTime ) {//On our way to a tempGoal float minGoalReachedDistSquared = 32*32; vec3_t vec; //Keep moving toward our tempGoal NPCInfo->goalEntity = NPCInfo->tempGoal; VectorSubtract ( NPCInfo->tempGoal->currentOrigin, NPC->currentOrigin, vec); if ( vec[2] < 24 ) { vec[2] = 0; } if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) { /* //FIXME: can't get the radius... float wpRadSq = waypoints[NPCInfo->tempGoal->waypoint].radius * waypoints[NPCInfo->tempGoal->waypoint].radius; if ( minGoalReachedDistSquared > wpRadSq ) { minGoalReachedDistSquared = wpRadSq; } */ minGoalReachedDistSquared = 32*32;//12*12; } if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) { //Close enough, just got there NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); if ( ( NPCInfo->homeWp == WAYPOINT_NONE ) || ( NPC->waypoint == WAYPOINT_NONE ) ) { //Heading for or at an invalid waypoint, get out of this bState if( NPCInfo->tempBehavior == BS_SEARCH ) {//if tempbehavior, set tempbehavior to default NPCInfo->tempBehavior = BS_DEFAULT; } else {//if bState, change to stand guard NPCInfo->behaviorState = BS_STAND_GUARD; NPC_BSRunAndShoot(); } return; } if ( NPC->waypoint == NPCInfo->homeWp ) { //Just Reached our homeWp, if this is the first time, run your lostenemyscript if ( NPCInfo->aiFlags & NPCAI_ENROUTE_TO_HOMEWP ) { NPCInfo->aiFlags &= ~NPCAI_ENROUTE_TO_HOMEWP; G_ActivateBehavior( NPC, BSET_LOSTENEMY ); } } //gi.Printf("Got there.\n"); //gi.Printf("Looking..."); if( !Q_irand(0, 1) ) { NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); } else { NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); } NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); } else { NPC_MoveToGoal( qtrue ); } } else { //We're there if ( NPCInfo->investigateDebounceTime > level.time ) { //Still waiting around for a bit //Turn angles every now and then to look around if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) { if ( !Q_irand( 0, 30 ) ) { int numEdges = navigator.GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); if ( numEdges != WAYPOINT_NONE ) { int branchNum = Q_irand( 0, numEdges - 1 ); vec3_t branchPos, lookDir; int nextWp = navigator.GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum ); navigator.GetNodePosition( nextWp, branchPos ); VectorSubtract( branchPos, NPCInfo->tempGoal->currentOrigin, lookDir ); NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) ); } //pick an angle +-45 degrees off of the dir of a random branch //from NPCInfo->tempGoal->waypoint //int branch = Q_irand( 0, (waypoints[NPCInfo->tempGoal->waypoint].numNeighbors - 1) ); //int nextWp = waypoints[NPCInfo->tempGoal->waypoint].nextWaypoint[branch][NPCInfo->stats.moveType]; //vec3_t lookDir; //VectorSubtract( waypoints[nextWp].origin, NPCInfo->tempGoal->currentOrigin, lookDir ); //Look in that direction +- 45 degrees //NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) ); } } //gi.Printf("."); } else {//Just finished waiting NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); if ( NPC->waypoint == NPCInfo->homeWp ) { int numEdges = navigator.GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); if ( numEdges != WAYPOINT_NONE ) { int branchNum = Q_irand( 0, numEdges - 1 ); int nextWp = navigator.GetNodeEdge( NPCInfo->homeWp, branchNum ); navigator.GetNodePosition( nextWp, NPCInfo->tempGoal->currentOrigin ); NPCInfo->tempGoal->waypoint = nextWp; } /* //Pick a random branch int branch = Q_irand( 0, (waypoints[NPCInfo->homeWp].numNeighbors - 1) ); int nextWp = waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPCInfo->stats.moveType]; VectorCopy( waypoints[nextWp].origin, NPCInfo->tempGoal->currentOrigin ); NPCInfo->tempGoal->waypoint = nextWp; //gi.Printf("\nHeading for wp %d...\n", waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPCInfo->stats.moveType]); */ } else {//At a branch, so return home navigator.GetNodePosition( NPCInfo->homeWp, NPCInfo->tempGoal->currentOrigin ); NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; /* VectorCopy( waypoints[NPCInfo->homeWp].origin, NPCInfo->tempGoal->currentOrigin ); NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; //gi.Printf("\nHeading for wp %d...\n", NPCInfo->homeWp); */ } NPCInfo->investigateDebounceTime = 0; //Start moving toward our tempGoal NPCInfo->goalEntity = NPCInfo->tempGoal; NPC_MoveToGoal( qtrue ); } } NPC_UpdateAngles( qtrue, qtrue ); }
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 }