/* ------------------------- NAVNEW_Bypass ------------------------- */ qboolean NAVNEW_Bypass( gentity_t *self, gentity_t *blocker, vec3_t blocked_dir, float blocked_dist, vec3_t movedir, qboolean setBlockedInfo ) { //Draw debug info if requested if ( NAVDEBUG_showCollision ) { CG_DrawEdge( self->currentOrigin, blocker->currentOrigin, EDGE_NORMAL ); } vec3_t moveangles, right; vectoangles( movedir, moveangles ); moveangles[2] = 0; AngleVectors( moveangles, NULL, right, NULL ); //Check to see what dir the other guy is moving in (if any) and pick the opposite dir if ( NAVNEW_DanceWithBlocker( self, blocker, movedir, right ) ) { return qtrue; } //Okay, so he's not moving to my side, see which side of him is most clear if ( NAVNEW_SidestepBlocker( self, blocker, blocked_dir, blocked_dist, movedir, right ) ) { return qtrue; } //Neither side is clear, tell him to step aside NAVNEW_PushBlocker( self, blocker, right, setBlockedInfo ); return qfalse; }
//////////////////////////////////////////////////////////////////////////////////////// // This func actually does the damage inflicting traces //////////////////////////////////////////////////////////////////////////////////////// void Boba_FireFlameThrower( gentity_t *self ) { trace_t tr; vec3_t start, end, dir; CVec3 traceMins(self->mins); CVec3 traceMaxs(self->maxs); gentity_t* traceEnt = NULL; int damage = Q_irand( BOBA_FLAMETHROWDAMAGEMIN, BOBA_FLAMETHROWDAMAGEMAX ); AngleVectors(self->currentAngles, dir, 0, 0); dir[2] = 0.0f; VectorCopy(self->currentOrigin, start); traceMins *= 0.5f; traceMaxs *= 0.5f; start[2] += 40.0f; VectorMA( start, 150.0f, dir, end ); if (g_bobaDebug->integer) { CG_DrawEdge(start, end, EDGE_IMPACT_POSSIBLE); } gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, MASK_SHOT, (EG2_Collision)0, 0); traceEnt = &g_entities[tr.entityNum]; if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) { G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE ); if (traceEnt->health>0) { // G_Knockdown( traceEnt, self, dir, Q_irand(200, 330), qfalse); G_Throw(traceEnt, dir, 30); } } }
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); }
bool Boba_Flee() { bool EnemyRecentlySeen = ((level.time - NPCInfo->enemyLastSeenTime)<10000); bool ReachedEscapePoint = (Distance(level.combatPoints[NPCInfo->combatPoint].origin, NPC->currentOrigin)<50.0f); bool HasBeenGoneEnough = (level.time>NPCInfo->surrenderTime || (level.time - NPCInfo->enemyLastSeenTime)>400000); // Is It Time To Come Back For Some More? //---------------------------------------- if (!EnemyRecentlySeen || ReachedEscapePoint) { NPC->svFlags |= SVF_NOCLIENT; if (HasBeenGoneEnough) { if ((level.time - NPCInfo->enemyLastSeenTime)>400000) { Boba_Printf(" Gone Too Long, Attempting Respawn"); } if (Boba_Respawn()) { return true; } } else if (ReachedEscapePoint && (NPCInfo->surrenderTime - level.time)>3000) { if (TIMER_Done(NPC, "SpookPlayerTimer")) { vec3_t testDirection; TIMER_Set(NPC, "SpookPlayerTimer", Q_irand(2000, 10000)); switch(Q_irand(0, 1)) { case 0: Boba_Printf("SPOOK: Dust"); Boba_DustFallNear(NPC->enemy->currentOrigin, Q_irand(1,2)); break; case 1: Boba_Printf("SPOOK: Footsteps"); testDirection[0] = (random() * 0.5f) - 1.0f; testDirection[0] += (testDirection[0]>0.0f)?(0.5f):(-0.5f); testDirection[1] = (random() * 0.5f) - 1.0f; testDirection[1] += (testDirection[1]>0.0f)?(0.5f):(-0.5f); testDirection[2] = 1.0f; VectorMA(NPC->enemy->currentOrigin, 400.0f, testDirection, BobaFootStepLoc); BobaFootStepCount = Q_irand(3,8); break; } } if (BobaFootStepCount && TIMER_Done(NPC, "BobaFootStepFakeTimer")) { TIMER_Set(NPC, "BobaFootStepFakeTimer", Q_irand(300, 800)); BobaFootStepCount --; G_SoundAtSpot(BobaFootStepLoc, G_SoundIndex(va("sound/player/footsteps/boot%d", Q_irand(1,4))), qtrue); } if (TIMER_Done(NPC, "ResampleEnemyDirection") && NPC->enemy->resultspeed>10.0f) { TIMER_Set(NPC, "ResampleEnemyDirection", Q_irand(500, 1000)); AverageEnemyDirectionSamples ++; vec3_t moveDir; VectorCopy(NPC->enemy->client->ps.velocity, moveDir); VectorNormalize(moveDir); VectorAdd(AverageEnemyDirection, moveDir, AverageEnemyDirection); } if (g_bobaDebug->integer && AverageEnemyDirectionSamples) { vec3_t endPos; VectorMA(NPC->enemy->currentOrigin, 500.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos); CG_DrawEdge(NPC->enemy->currentOrigin, endPos, EDGE_IMPACT_POSSIBLE); } } } else { NPCInfo->surrenderTime += 100; } // Finish The Flame Thrower First... //----------------------------------- if (NPCInfo->aiFlags&NPCAI_FLAMETHROW) { Boba_DoFlameThrower( NPC ); NPC_FacePosition( NPC->enemy->currentOrigin, qtrue); NPC_UpdateAngles(qtrue, qtrue); return true; } bool IsOnAPath = !!NPC_MoveToGoal(qtrue); if (!ReachedEscapePoint && NPCInfo->aiFlags&NPCAI_BLOCKED && NPC->client->moveType!=MT_FLYSWIM && ((level.time - NPCInfo->blockedDebounceTime)>1000) ) { if (!Boba_CanSeeEnemy(NPC) && Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<200) { Boba_Printf("BLOCKED: Just Teleporting There"); G_SetOrigin(NPC, level.combatPoints[NPCInfo->combatPoint].origin); } else { Boba_Printf("BLOCKED: Attempting Jump"); if (IsOnAPath) { if (NPC_TryJump(NPCInfo->blockedTargetPosition)) { } else { Boba_Printf(" Failed"); } } else if (EnemyRecentlySeen) { if (NPC_TryJump(NPCInfo->enemyLastSeenLocation)) { } else { Boba_Printf(" Failed"); } } } } NPC_UpdateAngles( qtrue, qtrue ); return true; }
void Boba_Update() { // Never Forget The Player... Never. //----------------------------------- if (player && player->inuse && !NPC->enemy) { G_SetEnemy(NPC, player); NPC->svFlags |= SVF_LOCKEDENEMY; // Don't forget about the enemy once you've found him } // Hey, This Is Boba, He Tests The Trace All The Time //---------------------------------------------------- if (NPC->enemy) { if (!(NPC->svFlags&SVF_NOCLIENT)) { trace_t testTrace; vec3_t eyes; CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); gi.trace (&testTrace, eyes, NULL, NULL, NPC->enemy->currentOrigin, NPC->s.number, MASK_SHOT, (EG2_Collision)0, 0); bool wasSeen = Boba_CanSeeEnemy(NPC); if (!testTrace.startsolid && !testTrace.allsolid && testTrace.entityNum == NPC->enemy->s.number) { NPCInfo->enemyLastSeenTime = level.time; NPCInfo->enemyLastHeardTime = level.time; VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation); VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation); } else if (gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin)) { NPCInfo->enemyLastHeardTime = level.time; VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation); } if (g_bobaDebug->integer) { bool nowSeen = Boba_CanSeeEnemy(NPC); if (!wasSeen && nowSeen) { Boba_Printf("Enemy Seen"); } if (wasSeen && !nowSeen) { Boba_Printf("Enemy Lost"); } CG_DrawEdge(NPC->currentOrigin, NPC->enemy->currentOrigin, (nowSeen)?(EDGE_IMPACT_SAFE):(EDGE_IMPACT_POSSIBLE)); } } if (!NPCInfo->surrenderTime) { if ((level.time - NPCInfo->enemyLastSeenTime)>20000 && TIMER_Done(NPC, "TooLongGoneRespawn")) { TIMER_Set(NPC, "TooLongGoneRespawn", 30000); // Give him some time to get to you before trying again Boba_Printf("Gone Too Long, Attempting Respawn Even Though Not Hiding"); Boba_Respawn(); } } } // Make Sure He Always Appears In The Last Area With Full Health When His Death Script Is Turned On //-------------------------------------------------------------------------------------------------- if (!BobaHadDeathScript && NPC->behaviorSet[BSET_DEATH]!=0) { if (!gi.inPVS(NPC->enemy->currentOrigin, NPC->currentOrigin)) { Boba_Printf("Attempting Final Battle Spawn..."); if (Boba_Respawn()) { BobaHadDeathScript = true; } else { Boba_Printf("Failed"); } } } // Don't Forget To Turn Off That Flame Thrower, Mr. Fett - You're Waisting Precious Natural Gases //------------------------------------------------------------------------------------------------ if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW) && (TIMER_Done(NPC, "flameTime"))) { Boba_StopFlameThrower(NPC); } // Occasionally A Jump Turns Into A Rocket Fly //--------------------------------------------- if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE && NPC->client->ps.forceJumpZStart && !Q_irand( 0, 10 ) ) {//take off Boba_FlyStart( NPC ); } // If Hurting, Try To Run Away //----------------------------- if (!NPCInfo->surrenderTime && (NPC->health<NPC->max_health/10)) { Boba_Printf("Time To Surrender, Searching For Flee Point"); // Find The Closest Flee Point That I Can Get To //----------------------------------------------- int cp = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_FLEE|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); if (cp!=-1) { NPC_SetCombatPoint( cp ); NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); if (NPC->count<6) { NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000) + 1000*(6-NPC->count); } else { NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000); } } else { Boba_Printf(" Failure"); } } }
/* ------------------------- NAVNEW_MoveToGoal ------------------------- */ int NAVNEW_MoveToGoal( gentity_t *self, navInfo_t &info ) { int bestNode = WAYPOINT_NONE; qboolean foundClearPath = qfalse; vec3_t origin; navInfo_t tempInfo; qboolean setBlockedInfo = qtrue; qboolean inGoalWP; int numTries = 0; memcpy( &tempInfo, &info, sizeof( tempInfo ) ); //Must have a goal entity to move there if( self->NPC->goalEntity == NULL ) return WAYPOINT_NONE; if ( self->waypoint == WAYPOINT_NONE && self->noWaypointTime > level.time ) {//didn't have a valid one in about the past second, don't look again just yet return WAYPOINT_NONE; } if ( self->NPC->goalEntity->waypoint == WAYPOINT_NONE && self->NPC->goalEntity->noWaypointTime > level.time ) {//didn't have a valid one in about the past second, don't look again just yet return WAYPOINT_NONE; } if ( self->noWaypointTime > level.time && self->NPC->goalEntity->noWaypointTime > level.time ) {//just use current waypoints bestNode = navigator.GetBestNodeAltRoute( self->waypoint, self->NPC->goalEntity->waypoint, bestNode ); } //FIXME!!!!: this is making them wiggle back and forth between waypoints else if ( (bestNode = navigator.GetBestPathBetweenEnts( self, self->NPC->goalEntity, NF_CLEAR_PATH )) == NODE_NONE )//!NAVNEW_GetWaypoints( self, qtrue ) ) {//one of us didn't have a valid waypoint! if ( self->waypoint == NODE_NONE ) {//don't even try to find one again for a bit self->noWaypointTime = level.time + Q_irand( 500, 1500 ); } if ( self->NPC->goalEntity->waypoint == NODE_NONE ) {//don't even try to find one again for a bit self->NPC->goalEntity->noWaypointTime = level.time + Q_irand( 500, 1500 ); } return WAYPOINT_NONE; } else { if ( self->NPC->goalEntity->noWaypointTime < level.time ) { self->NPC->goalEntity->noWaypointTime = level.time + Q_irand( 500, 1500 ); } } while( !foundClearPath ) { /*inBestWP = */inGoalWP = qfalse; /* bestNode = navigator.GetBestNodeAltRoute( self->waypoint, self->NPC->goalEntity->waypoint, bestNode ); */ if ( bestNode == WAYPOINT_NONE ) { goto failed; } //see if we can get directly to the next node off bestNode en route to goal's node... //NOTE: shouldn't be necc. now /* int oldBestNode = bestNode; bestNode = NAV_TestBestNode( self, self->waypoint, bestNode, qtrue );//, self->NPC->goalEntity->waypoint );// //NOTE: Guaranteed to return something if ( bestNode != oldBestNode ) {//we were blocked somehow if ( setBlockedInfo ) { self->NPC->aiFlags |= NPCAI_BLOCKED; navigator.GetNodePosition( oldBestNode, NPCInfo->blockedDest ); } } */ navigator.GetNodePosition( bestNode, origin ); /* if ( !goalWPFailed ) {//we haven't already tried to go straight to goal or goal's wp if ( bestNode == self->NPC->goalEntity->waypoint ) {//our bestNode is the goal's wp if ( NAV_HitNavGoal( self->currentOrigin, self->mins, self->maxs, origin, navigator.GetNodeRadius( bestNode ), FlyingCreature( self ) ) ) {//we're in the goal's wp inGoalWP = qtrue; //we're in the goalEntity's waypoint already //so head for the goalEntity since we know it's clear of architecture //FIXME: this is pretty stupid because the NPCs try to go straight // towards their goal before then even try macro_nav... VectorCopy( self->NPC->goalEntity->currentOrigin, origin ); } } } */ if ( !inGoalWP ) {//not heading straight for goal if ( bestNode == self->waypoint ) {//we know it's clear or architecture //navigator.GetNodePosition( self->waypoint, origin ); /* if ( NAV_HitNavGoal( self->currentOrigin, self->mins, self->maxs, origin, navigator.GetNodeRadius( bestNode ), FlyingCreature( self ) ) ) {//we're in the wp we're heading for already inBestWP = qtrue; } */ } else {//heading to an edge off our confirmed clear waypoint... make sure it's clear //it it's not, bestNode will fall back to our waypoint int oldBestNode = bestNode; bestNode = NAV_TestBestNode( self, self->waypoint, bestNode, qtrue ); if ( bestNode == self->waypoint ) {//we fell back to our waypoint, reset the origin self->NPC->aiFlags |= NPCAI_BLOCKED; navigator.GetNodePosition( oldBestNode, NPCInfo->blockedDest ); navigator.GetNodePosition( bestNode, origin ); } } } //Com_Printf( "goalwp = %d, mywp = %d, node = %d, origin = %s\n", self->NPC->goalEntity->waypoint, self->waypoint, bestNode, vtos(origin) ); memcpy( &tempInfo, &info, sizeof( tempInfo ) ); VectorSubtract( origin, self->currentOrigin, tempInfo.direction ); VectorNormalize( tempInfo.direction ); //NOTE: One very important thing NAVNEW_AvoidCollision does is // it actually CHANGES the value of "direction" - it changes it to // whatever dir you need to go in to avoid the obstacle... foundClearPath = NAVNEW_AvoidCollision( self, self->NPC->goalEntity, tempInfo, setBlockedInfo, 5 ); if ( !foundClearPath ) {//blocked by an ent if ( inGoalWP ) {//we were heading straight for the goal, head for the goal's wp instead navigator.GetNodePosition( bestNode, origin ); foundClearPath = NAVNEW_AvoidCollision( self, self->NPC->goalEntity, tempInfo, setBlockedInfo, 5 ); } } if ( foundClearPath ) {//clear! //If we got set to blocked, clear it NPC_ClearBlocked( self ); //Take the dir memcpy( &info, &tempInfo, sizeof( info ) ); if ( self->s.weapon == WP_SABER ) {//jedi if ( info.direction[2] * info.distance > 64 ) { self->NPC->aiFlags |= NPCAI_BLOCKED; VectorCopy( origin, NPCInfo->blockedDest ); goto failed; } } } else {//blocked by ent! if ( setBlockedInfo ) { self->NPC->aiFlags |= NPCAI_BLOCKED; navigator.GetNodePosition( bestNode, NPCInfo->blockedDest ); } //Only set blocked info first time setBlockedInfo = qfalse; if ( inGoalWP ) {//we headed for our goal and failed and our goal's WP and failed if ( self->waypoint == self->NPC->goalEntity->waypoint ) {//our waypoint is our goal's waypoint, nothing we can do //remember that this node is blocked navigator.AddFailedNode( self, self->waypoint ); goto failed; } else {//try going for our waypoint this time //goalWPFailed = qtrue; inGoalWP = qfalse; } } else if ( bestNode != self->waypoint ) {//we headed toward our next waypoint (instead of our waypoint) and failed if ( d_altRoutes->integer ) {//mark this edge failed and try our waypoint //NOTE: don't assume there is something blocking the direct path // between my waypoint and the bestNode... I could be off // that path because of collision avoidance... if ( d_patched->integer &&//use patch-style navigation ( !navigator.NodesAreNeighbors( self->waypoint, bestNode ) || NAVNEW_TestNodeConnectionBlocked( self->waypoint, bestNode, self, self->NPC->goalEntity->s.number, qfalse, qtrue ) ) ) {//the direct path between these 2 nodes is blocked by an ent navigator.AddFailedEdge( self->s.number, self->waypoint, bestNode ); } bestNode = self->waypoint; } else { //we should stop goto failed; } } else {//we headed for *our* waypoint and couldn't get to it if ( d_altRoutes->integer ) { //remember that this node is blocked navigator.AddFailedNode( self, self->waypoint ); //Now we should get our waypoints again //FIXME: cache the trace-data for subsequent calls as only the route info would have changed //if ( (bestNode = navigator.GetBestPathBetweenEnts( self, self->NPC->goalEntity, NF_CLEAR_PATH )) == NODE_NONE )//!NAVNEW_GetWaypoints( self, qfalse ) ) {//one of our waypoints is WAYPOINT_NONE now goto failed; } } else { //we should stop goto failed; } } if ( ++numTries >= 10 ) { goto failed; } } } //finish: //Draw any debug info, if requested if ( NAVDEBUG_showEnemyPath ) { vec3_t dest, start; //Get the positions navigator.GetNodePosition( self->NPC->goalEntity->waypoint, dest ); navigator.GetNodePosition( bestNode, start ); //Draw the route CG_DrawNode( start, NODE_START ); if ( bestNode != self->waypoint ) { vec3_t wpPos; navigator.GetNodePosition( self->waypoint, wpPos ); CG_DrawNode( wpPos, NODE_NAVGOAL ); } CG_DrawNode( dest, NODE_GOAL ); CG_DrawEdge( dest, self->NPC->goalEntity->currentOrigin, EDGE_PATH ); CG_DrawNode( self->NPC->goalEntity->currentOrigin, NODE_GOAL ); navigator.ShowPath( bestNode, self->NPC->goalEntity->waypoint ); } self->NPC->shoveCount = 0; //let me keep this waypoint for a while if ( self->noWaypointTime < level.time ) { self->noWaypointTime = level.time + Q_irand( 500, 1500 ); } return bestNode; failed: //FIXME: What we should really do here is have a list of the goal's and our // closest clearpath waypoints, ranked. If the first set fails, try the rest // until there are no alternatives. navigator.GetNodePosition( self->waypoint, origin ); //do this to avoid ping-ponging? return WAYPOINT_NONE; /* //this was causing ping-ponging if ( DistanceSquared( origin, self->currentOrigin ) < 16 )//woo, magic number {//We're right up on our waypoint, so that won't help, return none //Or maybe find the nextbest here? return WAYPOINT_NONE; } else {//Try going to our waypoint bestNode = self->waypoint; VectorSubtract( origin, self->currentOrigin, info.direction ); VectorNormalize( info.direction ); } goto finish; */ }
/* ------------------------- NAVNEW_AvoidCollision ------------------------- */ qboolean NAVNEW_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t &info, qboolean setBlockedInfo, int blockedMovesLimit ) { vec3_t movedir; vec3_t movepos; //Cap our distance if ( info.distance > MAX_COLL_AVOID_DIST ) { info.distance = MAX_COLL_AVOID_DIST; } //Get an end position VectorMA( self->currentOrigin, info.distance, info.direction, movepos ); VectorCopy( info.direction, movedir ); //Now test against entities if ( NAV_CheckAhead( self, movepos, info.trace, CONTENTS_BODY ) == qfalse ) { //Get the blocker info.blocker = &g_entities[ info.trace.entityNum ]; info.flags |= NIF_COLLISION; //Ok to hit our goal entity if ( goal == info.blocker ) return qtrue; if ( setBlockedInfo ) { if ( self->NPC->consecutiveBlockedMoves > blockedMovesLimit ) { if ( d_patched->integer ) {//use patch-style navigation self->NPC->consecutiveBlockedMoves++; } NPC_SetBlocked( self, info.blocker ); return qfalse; } self->NPC->consecutiveBlockedMoves++; } //See if we're moving along with them //if ( NAVNEW_TrueCollision( self, info.blocker, movedir, info.direction ) == qfalse ) // return qtrue; //Test for blocking by standing on goal if ( NAV_TestForBlocked( self, goal, info.blocker, info.distance, info.flags ) == qtrue ) return qfalse; //If the above function said we're blocked, don't do the extra checks /* if ( info.flags & NIF_BLOCKED ) return qtrue; */ //See if we can get that entity to move out of our way if ( NAVNEW_ResolveEntityCollision( self, info.blocker, movedir, info.pathDirection, setBlockedInfo ) == qfalse ) return qfalse; VectorCopy( movedir, info.direction ); return qtrue; } else { if ( setBlockedInfo ) { self->NPC->consecutiveBlockedMoves = 0; } } //Our path is clear, just move there if ( NAVDEBUG_showCollision ) { CG_DrawEdge( self->currentOrigin, movepos, EDGE_MOVEDIR ); } return qtrue; }
void Rail_Update() { if (mRailSystemActive)// && false) { for (int track=0; track<mRailTracks.size(); track++) { if (level.time>mRailTracks[track].mNextUpdateTime && !mRailTracks[track].mMovers.empty()) { mRailTracks[track].Update(); } } // Is The Player Outside? //------------------------ if (player && gi.WE_IsOutside(player->currentOrigin)) { int wooshSound; vec3_t wooshSoundPos; vec3_t moverOrigin; vec3_t playerToMover; float playerToMoverDistance; float playerToMoverDistanceFraction; // Iterate Over All The Movers //----------------------------- for (int moverIndex=0; moverIndex<mRailMovers.size(); moverIndex++) { CRailMover& mover = mRailMovers[moverIndex]; // Is It Active, And Has The Sound Already Played On It? //-------------------------------------------------------- if (mover.Active() && !mover.mSoundPlayed) { VectorAdd(mover.mEnt->currentOrigin, mover.mOriginOffset.v, moverOrigin); VectorSubtract(moverOrigin, player->currentOrigin, playerToMover); playerToMover[2] = 0.0f; playerToMoverDistance = VectorNormalize(playerToMover); // Is It Close Enough? //--------------------- if ((( mover.mLane || !mover.mCenter) && // Not Center Track (playerToMoverDistance<WOOSH_ALL_RANGE) && // And Close Enough (DotProduct(playerToMover, mover.mTrack->mDirection.v)>-0.45f)) // And On The Side || //OR ((!mover.mLane && mover.mCenter) && // Is Center Track (playerToMoverDistance<WOOSH_SUPPORT_RANGE || // And Close Enough for Support (playerToMoverDistance<WOOSH_TUNNEL_RANGE && mover.mRows>10)) // Or Close Enough For Tunnel )) { mover.mSoundPlayed = true; wooshSound = 0; // The Centered Entities Play Right On The Player's Head For Full Volume //----------------------------------------------------------------------- if (mover.mCenter && !mover.mLane) { VectorCopy(player->currentOrigin, wooshSoundPos); wooshSoundPos[2] += 50; // If It Is Very Long, Play The Tunnel Sound //------------------------------------------- if (mover.mRows>10) { wooshSound = mWooshTun[Q_irand(0, mWooshTun.size()-1)]; } // Otherwise It Is A Support //--------------------------- else { wooshSound = mWooshSup[Q_irand(0, mWooshSup.size()-1)]; } } // All Other Entities Play At A Fraction Of Their Normal Range //------------------------------------------------------------- else { // Scale The Play Pos By The Square Of The Distance //-------------------------------------------------- playerToMoverDistanceFraction = playerToMoverDistance/WOOSH_ALL_RANGE; playerToMoverDistanceFraction *= playerToMoverDistanceFraction; playerToMoverDistanceFraction *= 0.6f; playerToMoverDistance *= playerToMoverDistanceFraction; VectorMA(player->currentOrigin, playerToMoverDistance, playerToMover, wooshSoundPos); // Large Building //---------------- if (mover.mRows>4) { wooshSound = mWooshLar[Q_irand(0, mWooshLar.size()-1)]; } // Medium Building //----------------- else if (mover.mRows>2) { wooshSound = mWooshMed[Q_irand(0, mWooshMed.size()-1)]; } // Small Building //---------------- else { wooshSound = mWooshSml[Q_irand(0, mWooshSml.size()-1)]; } } // If A Woosh Sound Was Selected, Play It Now //-------------------------------------------- if (wooshSound) { G_SoundAtSpot(wooshSoundPos, wooshSound, qfalse); if (WOOSH_DEBUG) { CG_DrawEdge(player->currentOrigin, wooshSoundPos, EDGE_WHITE_TWOSECOND); } } } } } } } }