// Autopilot.cpp // Derek Meek // 4-30-2004 #include "autopilot/autopilot.h" #include "ai/ai.h" #include "ai/aigoals.h" #include "ship/ship.h" #include "object/object.h" #include "object/objcollide.h" #include "object/waypoint.h" #include "parse/sexp.h" #include "freespace2/freespace.h" #include "globalincs/linklist.h" #include "iff_defs/iff_defs.h" #include "sound/audiostr.h" #include "mission/missiontraining.h" #include "mission/missionmessage.h" #include "io/timer.h" #include "gamesnd/eventmusic.h" #include "cfile/cfile.h" #include "parse/parselo.h" #include "globalincs/def_files.h" #include "localization/localize.h" #include "camera/camera.h" #include "asteroid/asteroid.h" #include // Extern functions/variables extern int Player_use_ai; extern int get_wing_index(object *objp, int wingnum); extern object * get_wing_leader(int wingnum); extern int Cmdline_autopilot_interruptable; // Module variables bool AutoPilotEngaged; bool UseCutsceneBars; int CurrentNav; float ramp_bias; NavPoint Navs[MAX_NAVPOINTS]; NavMessage NavMsgs[NP_NUM_MESSAGES]; int audio_handle; int NavLinkDistance; // time offsets for autonav events int LockAPConv; int EndAPCinematic; int MoveCamera; int camMovingTime; //float CameraSpeed; bool CinematicStarted, CameraMoving; vec3d cameraPos, cameraTarget; std::map autopilot_wings; // used for ramping time compression; int start_dist; // ******************************************************************************************** void autopilot_ai_waypoint_goal_fixup(ai_goal* aigp) { // this function sets wp_index properly; for (int i = 0; i < Num_waypoint_lists; i++) { if (!stricmp(aigp->ship_name, Waypoint_lists[i].name)) { aigp->wp_index = i; return; } } } // ******************************************************************************************** bool Sel_NextNav() { if (AutoPilotEngaged) return false; int i; if (CurrentNav == -1) { for (i = 0; i < MAX_NAVPOINTS; i++) { if (Navs[i].flags & NP_VALIDTYPE && !(Navs[i].flags & NP_NOSELECT)) { CurrentNav=i; return true; } } } else { for (i = CurrentNav+1; i < MAX_NAVPOINTS+CurrentNav; i++) { if (Navs[i%MAX_NAVPOINTS].flags & NP_VALIDTYPE && !(Navs[i%MAX_NAVPOINTS].flags & NP_NOSELECT)) { if (i != CurrentNav) { CurrentNav=i%MAX_NAVPOINTS; return true; } } } } return false; } // ******************************************************************************************** vec3d *NavPoint::GetPosition() { if (flags & NP_WAYPOINT) { return &((waypoint_list*) target_obj)->waypoints[waypoint_num-1]; } else { return &Objects[((ship*) target_obj)->objnum].pos; } } char* NavPoint::GetInteralName() { char *NavName; char strtmp[33]; if (flags & NP_WAYPOINT) { NavName = new char[strlen(((waypoint_list*)target_obj)->name)+5]; memset(NavName, 0, strlen(((waypoint_list*)target_obj)->name)+5); strcpy(NavName, ((waypoint_list*)target_obj)->name); strcat(NavName, ":"); sprintf(strtmp, "%d", waypoint_num); strcat(NavName, strtmp); } else { NavName = new char[strlen(((ship*)target_obj)->ship_name)+1]; memset(NavName, 0, strlen(((ship*)target_obj)->ship_name)+1); strcpy(NavName, ((ship*)target_obj)->ship_name); } return NavName; } // ******************************************************************************************** // Tell us if autopilot is allowed // This needs: // * Nav point selected // * No enemies within X distance // * Destination > 1,000 meters away bool CanAutopilot(bool send_msg) { if (CurrentNav == -1) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_NOSEL); return false; } if (object_get_gliding(Player_obj)) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_GLIDING); return false; } // You cannot autopilot if you're within 1000 meters of your destination nav point if (vm_vec_dist_quick(&Player_obj->pos, Navs[CurrentNav].GetPosition()) < 1000) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_TOCLOSE); return false; } // see if any hostiles are nearby for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { object *other_objp = &Objects[so->objnum]; // attacks player? if (iff_x_attacks_y(obj_team(other_objp), obj_team(Player_obj)) && !(Ship_info[Ships[other_objp->instance].ship_info_index].flags & SIF_CARGO)) { // Cannot autopilot if enemy within 5,000 meters if (vm_vec_dist_quick(&Player_obj->pos, &other_objp->pos) < 5000) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_HOSTILES); return false; } } } //check for asteroids for (int n=0; npos, &Objects[Asteroids[n].objnum].pos) < 1000) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_HAZARD); return false; } } } return true; } // ******************************************************************************************** // Tell us if autopilot is allowed at a certain position (only performs checks based on that position) // This needs: // * Nav point selected // * No enemies within X distance // * Destination > 1,000 meters away bool CanAutopilotPos(vec3d targetPos) { // You cannot autopilot if you're within 1000 meters of your destination nav point if (vm_vec_dist_quick(&targetPos, Navs[CurrentNav].GetPosition()) < 1000) return false; // see if any hostiles are nearby for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) { object *other_objp = &Objects[so->objnum]; // attacks player? if (iff_x_attacks_y(obj_team(other_objp), obj_team(Player_obj)) && !(Ship_info[Ships[other_objp->instance].ship_info_index].flags & SIF_CARGO)) // ignore cargo { // Cannot autopilot if enemy within 5,000 meters if (vm_vec_dist_quick(&targetPos, &other_objp->pos) < 5000) return false; } } //check for asteroids for (int n=0; nradius, distance = 0.0f, ftemp; bool capshipPresent = false; int capship_counts[3]; // three size classes capship_counts[0] = 0; capship_counts[1] = 0; capship_counts[2] = 0; int capship_placed[3]; // three size classes capship_placed[0] = 0; capship_placed[1] = 0; capship_placed[2] = 0; float capship_spreads[3]; capship_spreads[0] = 0.0f; capship_spreads[1] = 0.0f; capship_spreads[2] = 0.0f; SCP_vector capIndexes; // empty the autopilot wings map autopilot_wings.clear(); // vars for usage w/ cinematic vec3d pos, norm1, perp, tpos, rpos = Player_obj->pos, zero; memset(&zero, 0, sizeof(vec3d)); // instantly turn player toward tpos if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) { vm_vec_sub(&norm1, Navs[CurrentNav].GetPosition(), &Player_obj->pos); vm_vector_2_matrix(&Player_obj->orient, &norm1, NULL, NULL); } for (i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && (Ships[i].flags2 & SF2_NAVPOINT_CARRY || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY) ) ) { if (speed_cap > vm_vec_mag(&Ship_info[Ships[i].ship_info_index].max_vel)) speed_cap = vm_vec_mag(&Ship_info[Ships[i].ship_info_index].max_vel); } } // damp speed_cap to 90% of actual -- to make sure ships stay in formation if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) speed_cap = 0.90f * speed_cap; ramp_bias = speed_cap/50.0f; // assign ship goals // when assigning goals to individual ships only do so if Ships[shipnum].wingnum != -1 // we will assign wing goals below for (i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && (Ships[i].flags2 & SF2_NAVPOINT_CARRY || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY) ) ) { // do we have capital ships in the area? if (Ship_info[Ships[i].ship_info_index].flags & ( SIF_CRUISER | SIF_CAPITAL | SIF_SUPERCAP | SIF_CORVETTE | SIF_AWACS | SIF_GAS_MINER | SIF_FREIGHTER | SIF_TRANSPORT)) { capshipPresent = true; capIndexes.resize(capIndexes.size()+1); capIndexes[capIndexes.size()-1] = i; // ok.. what size class if (Ship_info[Ships[i].ship_info_index].flags & (SIF_CAPITAL | SIF_SUPERCAP)) { capship_counts[0]++; if (capship_spreads[0] < Objects[Ships[i].objnum].radius) capship_spreads[0] = Objects[Ships[i].objnum].radius; } else if (Ship_info[Ships[i].ship_info_index].flags & (SIF_CORVETTE)) { capship_counts[1]++; if (capship_spreads[1] < Objects[Ships[i].objnum].radius) capship_spreads[1] = Objects[Ships[i].objnum].radius; } else { capship_counts[2]++; if (capship_spreads[2] < Objects[Ships[i].objnum].radius) capship_spreads[2] = Objects[Ships[i].objnum].radius; } } // check for bigger radius for usage later /*if (!vm_vec_cmp(&rpos, &Player_obj->pos)) // want to make sure rpos isn't player pos - we can worry about it being largest object's later { rpos = Objects[Ships[i].objnum].pos; }*/ if (Objects[Ships[i].objnum].radius > radius) { rpos = Objects[Ships[i].objnum].pos; radius = Objects[Ships[i].objnum].radius; } if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) {// instantly turn the ship to match the direction player is looking //vm_vec_sub(&norm1, Navs[CurrentNav].GetPosition(), &Player_obj->pos); vm_vector_2_matrix(&Objects[Ships[i].objnum].orient, &norm1, NULL, NULL); } // snap wings into formation them into formation if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS && // only if using cinematics (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY) // only if in a wing && Player_obj != &Objects[Ships[i].objnum]) //only if not player object { ai_info *aip = &Ai_info[Ships[i].ai_index]; int wingnum = aip->wing, wing_index = get_wing_index(&Objects[Ships[i].objnum], wingnum); vec3d goal_point; object *leader_objp = get_wing_leader(wingnum); if (leader_objp != &Objects[Ships[i].objnum]) { // not leader.. get our position relative to leader get_absolute_wing_pos_autopilot(&goal_point, leader_objp, wing_index, aip->ai_flags & AIF_FORMATION_OBJECT); } else { ai_clear_wing_goals(wingnum); j = 1+int( (float)floor(double(wcount-1)/2.0) ); switch (wcount % 2) { case 1: // back-left vm_vec_add(&perp, &zero, &Player_obj->orient.vec.rvec); //vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.fvec); vm_vec_normalize(&perp); vm_vec_scale(&perp, -166.0f*j); // 166m is supposedly the optimal range according to tolwyn vm_vec_add(&goal_point, &Player_obj->pos, &perp); break; default: //back-right case 0: vm_vec_add(&perp, &zero, &Player_obj->orient.vec.rvec); //vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.fvec); vm_vec_normalize(&perp); vm_vec_scale(&perp, 166.0f*j); vm_vec_add(&goal_point, &Player_obj->pos, &perp); break; } autopilot_wings[wingnum] = wcount; wcount++; } Objects[Ships[i].objnum].pos = goal_point; if (vm_vec_dist_quick(&Player_obj->pos, &Objects[Ships[i].objnum].pos) > distance) { distance = vm_vec_dist_quick(&Player_obj->pos, &Objects[Ships[i].objnum].pos); } } // lock primary and secondary weapons //Ships[i].flags2 |= (SF2_PRIMARIES_LOCKED | SF2_SECONDARIES_LOCKED); // clear the ship goals and cap the waypoint speed ai_clear_ship_goals(&Ai_info[Ships[i].ai_index]); Ai_info[Ships[i].ai_index].waypoint_speed_cap = (int)speed_cap; // if they're not part of a wing set their goal if (Ships[i].wingnum == -1 || The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) { if (Navs[CurrentNav].flags & NP_WAYPOINT) { ai_add_ship_goal_player( AIG_TYPE_PLAYER_SHIP, AI_GOAL_WAYPOINTS_ONCE, 0, ((waypoint_list*)Navs[CurrentNav].target_obj)->name, &Ai_info[Ships[i].ai_index] ); //fixup has to wait until after wing goals } else { ai_add_ship_goal_player( AIG_TYPE_PLAYER_SHIP, AI_GOAL_FLY_TO_SHIP, 0, ((ship*)Navs[CurrentNav].target_obj)->ship_name, &Ai_info[Ships[i].ai_index] ); } } } } // assign wing goals if (!(The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)) { for (i = 0; i < MAX_WINGS; i++) { if (Wings[i].flags & WF_NAV_CARRY ) { //ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip ); //ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_STAY_NEAR_SHIP, 0, target_shipname, wingnum ); //ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_WAYPOINTS_ONCE, 0, target_shipname, wingnum ); //ai_clear_ship_goals( &(Ai_info[Ships[num].ai_index]) ); ai_clear_wing_goals( i ); if (Navs[CurrentNav].flags & NP_WAYPOINT) { ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_WAYPOINTS_ONCE, 0, ((waypoint_list*)Navs[CurrentNav].target_obj)->name, i ); // "fix up" the goal for (j = 0; j < MAX_AI_GOALS; j++) { if (Wings[i].ai_goals[j].ai_mode == AI_GOAL_WAYPOINTS_ONCE || Wings[i].ai_goals[j].ai_mode == AIM_WAYPOINTS ) { autopilot_ai_waypoint_goal_fixup(&(Wings[i].ai_goals[j])); } } } else { ai_add_wing_goal_player( AIG_TYPE_PLAYER_WING, AI_GOAL_FLY_TO_SHIP, 0, ((ship*)Navs[CurrentNav].target_obj)->ship_name, i ); } } } } // fixup has to go down here because ships are assigned goals during wing goals as well for (i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1) { if (Ships[i].flags2 & SF2_NAVPOINT_CARRY || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY)) for (j = 0; j < MAX_AI_GOALS; j++) { if (Ai_info[Ships[i].ai_index].goals[j].ai_mode == AI_GOAL_WAYPOINTS_ONCE || Ai_info[Ships[i].ai_index].goals[j].ai_mode == AIM_WAYPOINTS) { autopilot_ai_waypoint_goal_fixup( &(Ai_info[Ships[i].ai_index].goals[j]) ); // formation fixup //ai_form_on_wing(&Objects[Ships[i].objnum], &Objects[Player_ship->objnum]); } } } } start_dist = DistanceTo(CurrentNav); // ----------------------------- setup cinematic ----------------------------- if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) { if (capshipPresent) { // position capships vec3d right, front, up, offset; for (i = 0; i < (int)capIndexes.size(); i++) { vm_vec_add(&right, &Player_obj->orient.vec.rvec, &zero); vm_vec_add(&front, &Player_obj->orient.vec.fvec, &zero); vm_vec_add(&up, &Player_obj->orient.vec.uvec, &zero); vm_vec_add(&offset, &zero, &zero); if (Ship_info[Ships[capIndexes[i]].ship_info_index].flags & (SIF_CAPITAL | SIF_SUPERCAP)) { //0 - below - three lines of position // front/back to zero vm_vec_add(&front, &zero, &zero); // position below vm_vec_scale(&up, capship_spreads[0]); // scale the up vector by the radius of the largest ship in this formation part switch (capship_placed[0] % 3) { case 1: // right vm_vec_scale(&right, capship_spreads[0]); break; case 2: // left vm_vec_scale(&right, -capship_spreads[0]); break; default: // straight case 0: vm_vec_add(&right, &zero, &zero); vm_vec_scale(&up, 1.5); // add an extra half-radius break; } // scale by row vm_vec_scale(&right, (1+((float)floor((float)capship_placed[0]/3)))); vm_vec_scale(&up, -(1+((float)floor((float)capship_placed[0]/3)))); capship_placed[0]++; } else if (Ship_info[Ships[capIndexes[i]].ship_info_index].flags & SIF_CORVETTE) { //1 above - 3 lines of position // front/back to zero vm_vec_add(&front, &zero, &zero); // position below vm_vec_scale(&up, capship_spreads[1]); // scale the up vector by the radius of the largest ship in this formation part switch (capship_placed[1] % 3) { case 1: // right vm_vec_scale(&right, capship_spreads[1]); break; case 2: // left vm_vec_scale(&right, -capship_spreads[1]); break; default: // straight case 0: vm_vec_add(&right, &zero, &zero); vm_vec_scale(&up, 1.5); // add an extra half-radius break; } // scale by row vm_vec_scale(&right, (1+((float)floor((float)capship_placed[1]/3)))); vm_vec_scale(&up, (1+((float)floor((float)capship_placed[1]/3)))); // move ourselves up and out of the way of the smaller ships vm_vec_add(&perp, &Player_obj->orient.vec.uvec, &zero); vm_vec_scale(&perp, capship_spreads[2]); vm_vec_add(&up, &up, &perp); capship_placed[1]++; } else { //2 either side - 6 lines of position (right (dir, front, back), left (dir, front, back) ) // placing pattern: right, left, front right, front left, rear right, rear left // up/down to zero vm_vec_add(&up, &zero, &zero); switch (capship_placed[2] % 6) { case 5: // rear left vm_vec_scale(&right, -capship_spreads[2]); vm_vec_scale(&front, -capship_spreads[2]); break; case 4: // rear right vm_vec_scale(&right, capship_spreads[2]); vm_vec_scale(&front, -capship_spreads[2]); break; case 3: // front left vm_vec_scale(&right, -capship_spreads[2]); vm_vec_scale(&front, capship_spreads[2]); break; case 2: // front right vm_vec_scale(&right, capship_spreads[2]); vm_vec_scale(&front, capship_spreads[2]); break; case 1: // straight left vm_vec_scale(&right, 1.5); vm_vec_scale(&right, -capship_spreads[2]); vm_vec_add(&front, &zero, &zero); break; default: // straight right case 0: vm_vec_scale(&right, 1.5); vm_vec_scale(&right, capship_spreads[2]); vm_vec_add(&front, &zero, &zero); break; } // these ships seem to pack a little too tightly vm_vec_scale(&right, 2*(1+((float)floor((float)capship_placed[2]/3)))); vm_vec_scale(&front, 2*(1+((float)floor((float)capship_placed[2]/3)))); // move "out" by 166*(wcount-1) so we don't bump into fighters vm_vec_add(&perp, &Player_obj->orient.vec.rvec, &zero); vm_vec_scale(&perp, 166.0f*float(wcount-1)); if ( (capship_placed[2] % 2) == 0) vm_vec_add(&right, &right, &perp); else vm_vec_sub(&right, &right, &perp); capship_placed[2]++; } // integrate the up/down componant vm_vec_add(&offset, &offset, &up); //integrate the left/right componant vm_vec_add(&offset, &offset, &right); //integrate the left/right componant vm_vec_add(&offset, &offset, &front); // global scale the position by 50% //vm_vec_scale(&offset, 1.5); vm_vec_add(&Objects[Ships[capIndexes[i]].objnum].pos, &Player_obj->pos, &offset); if (vm_vec_dist_quick(&Player_obj->pos, &Objects[Ships[i].objnum].pos) > distance) { distance = vm_vec_dist_quick(&Player_obj->pos, &Objects[Ships[i].objnum].pos); } } } ftemp = floor(Player_obj->phys_info.max_vel.xyz.z/speed_cap); if (ftemp >= 2.0f && ftemp < 4.0f) tc_factor = 2; else if (ftemp >= 4.0f && ftemp < 8.0f) tc_factor = 4; else if (ftemp >= 8.0f) tc_factor = 8; tpos = *Navs[CurrentNav].GetPosition(); // determine distance toward nav at which camera will be vm_vec_sub(&pos, &tpos, &Player_obj->pos); vm_vec_normalize(&pos); // pos is now a unit vector in the direction we will be moving the camera //norm1 = pos; vm_vec_scale(&pos, 5*speed_cap*tc_factor); // pos is now scaled by 5 times the speed (5 seconds ahead) vm_vec_add(&pos, &pos, &Player_obj->pos); // pos is now 5*speed cap in front of player ship switch (myrand()%24) // 8 different ways of getting perp points // 4 of which will not be used when capships are present (anything below, or straight above) { case 1: // down case 9: case 16: if (capship_placed[0] == 0) vm_vec_sub(&perp, &zero, &Player_obj->orient.vec.uvec); else { // become up-left vm_vec_add(&perp, &zero, &Player_obj->orient.vec.uvec); vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.rvec); } break; case 2: // up case 10: case 23: vm_vec_add(&perp, &perp, &Player_obj->orient.vec.uvec); if (capshipPresent) // become up-right vm_vec_add(&perp, &perp, &Player_obj->orient.vec.rvec); break; case 3: // left case 11: case 22: vm_vec_sub(&perp, &zero, &Player_obj->orient.vec.rvec); break; case 4: // up-left case 12: case 21: vm_vec_sub(&perp, &zero, &Player_obj->orient.vec.rvec); vm_vec_add(&perp, &perp, &Player_obj->orient.vec.uvec); break; case 5: // up-right case 13: case 20: vm_vec_add(&perp, &zero, &Player_obj->orient.vec.rvec); vm_vec_add(&perp, &perp, &Player_obj->orient.vec.uvec); break; case 6: // down-left case 14: case 19: vm_vec_sub(&perp, &zero, &Player_obj->orient.vec.rvec); if (capship_placed[0] < 2) vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.uvec); else vm_vec_add(&perp, &perp, &Player_obj->orient.vec.uvec); break; case 7: // down-right case 15: case 18: vm_vec_add(&perp, &zero, &Player_obj->orient.vec.rvec); if (capship_placed[0] < 1) vm_vec_sub(&perp, &perp, &Player_obj->orient.vec.uvec); else vm_vec_add(&perp, &perp, &Player_obj->orient.vec.uvec); break; default: case 0: // right case 8: case 17: perp = Player_obj->orient.vec.rvec; break; } vm_vec_normalize(&perp); //vm_vec_scale(&perp, 2*radius+distance); vm_vec_scale(&perp, Player_obj->radius+radius); // randomly scale up/down by up to 20% j = 20-myrand()%40; // [-20,20] vm_vec_scale(&perp, 1.0f+(float(j)/100.0f)); vm_vec_add(&cameraPos, &pos, &perp); if (capshipPresent) { vm_vec_normalize(&perp); // place it behind //vm_vec_copy_scale(&norm1, &Player_obj->orient.vec.fvec, -2*(Player_obj->radius+radius*(1.0f+(float(j)/100.0f)))); //vm_vec_add(&cameraTarget, &cameraTarget, &norm1); vm_vec_copy_scale(&cameraTarget,&perp, radius/5.0f); //vm_vec_scale(&cameraTarget, Player_obj->radius+radius*(1.0f+(float(j)/100.0f))); //vm_vec_add(&cameraTarget, &pos, &cameraTarget); //CameraSpeed = (radius+distance)/25; //vm_vec_add(&cameraTarget, &zero, &perp); //vm_vec_scale(&CameraVelocity, (radius+distance/100.f)); //vm_vec_scale(&CameraVelocity, 1.0f/float(NPS_TICKRATE*tc_factor)); } else { vm_vec_add(&cameraTarget, &zero, &zero); //CameraSpeed = 0; } //CameraMoving = false; EndAPCinematic = timestamp((10000*tc_factor)+NPS_TICKRATE); // 10 objective seconds before end of cinematic MoveCamera = timestamp((5500*tc_factor)+NPS_TICKRATE); camMovingTime = int(4.5*float(tc_factor)); set_time_compression((float)tc_factor); } } // ******************************************************************************************** // Checks if autopilot should automatically die // Returns true if: // * Targetted waypoint < 10,000 meters away // * Enemy < 10,000 meters bool Autopilot_AutoDiable() { return !CanAutopilot(); } // ******************************************************************************************** // Disengages autopilot // this does: // * Time compression to 1x // * Delete AI nav goal // * Control switched from AI to player void EndAutoPilot() { AutoPilotEngaged = false; set_time_compression(1); lock_time_compression(false); Player_use_ai = 0; //Clear AI Goals if (CinematicStarted) // clear cinematic if we need to { if (UseCutsceneBars) { Cutscene_bar_flags &= ~CUB_CUTSCENE; } cam_reset_camera(); CinematicStarted = false; } Assert( CurrentNav >= 0 ); int goal = 0; char *goal_name = NULL; if (Navs[CurrentNav].flags & NP_WAYPOINT) { goal = AI_GOAL_WAYPOINTS_ONCE; goal_name = ((waypoint_list*)Navs[CurrentNav].target_obj)->name; } else { goal = AI_GOAL_FLY_TO_SHIP; goal_name = ((ship*)Navs[CurrentNav].target_obj)->ship_name; } // assign ship goals // when assigning goals to individual ships only do so if Ships[shipnum].wingnum != -1 // we will assign wing goals below int i, j; for (i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && ( Ships[i].flags2 & SF2_NAVPOINT_CARRY || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY ) ) ) { //unlock their weaponry //Ships[i].flags2 &= ~(SF2_PRIMARIES_LOCKED | SF2_SECONDARIES_LOCKED); Ai_info[Ships[i].ai_index].waypoint_speed_cap = -1; // uncap their speed for (j = 0; j < MAX_AI_GOALS; j++) { ai_goal *aigp = &Ai_info[Ships[i].ai_index].goals[j]; if ( ((aigp->ship_name != NULL) && !stricmp(aigp->ship_name, goal_name)) && (aigp->ai_mode == goal) ) { ai_remove_ship_goal(&Ai_info[Ships[i].ai_index], j); } } if (Ships[i].wingnum != -1) { for (j = 0; j < MAX_AI_GOALS; j++) { ai_goal *aigp = &Wings[Ships[i].wingnum].ai_goals[j]; if ( ((aigp->ship_name != NULL) && !stricmp(aigp->ship_name, goal_name)) && (aigp->ai_mode == goal) ) { aigp->ai_mode = AI_GOAL_NONE; aigp->signature = -1; aigp->priority = -1; aigp->flags = 0; } } } } } // un-assign wing goals if ( !(The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) ) { for (i = 0; i < MAX_WINGS; i++) { if (Wings[i].flags & WF_NAV_CARRY ) { for (j = 0; j < MAX_AI_GOALS; j++) { ai_goal *aigp = &Wings[i].ai_goals[j]; if ( ((aigp->ship_name != NULL) && !stricmp(aigp->ship_name, goal_name)) && (aigp->ai_mode == goal) ) { aigp->ai_mode = AI_GOAL_NONE; aigp->signature = -1; aigp->priority = -1; aigp->flags = 0; } } } } } } // ******************************************************************************************** camera* nav_get_set_camera() { static camid nav_camera; if(!nav_camera.isValid()) { nav_camera = cam_create("Nav camera"); } cam_set_camera(nav_camera); return nav_camera.getCamera(); } void nav_warp(bool prewarp=false) { // ok... find our end distance - norm1 is still a unit vector in the direction from the player to the navpoint vec3d targetPos, tpos=Player_obj->pos, pos; vm_vec_sub(&pos, Navs[CurrentNav].GetPosition(), &Player_obj->pos); vm_vec_normalize(&pos); vm_vec_scale(&pos, 250.0f); // we move by increments of 250 while (CanAutopilotPos(tpos)) { vm_vec_add(&tpos, &tpos, &pos); } vm_vec_sub(&targetPos, &tpos, &Player_obj->pos); //targetPos is actually a projection in a the direction toward the nav if (prewarp) { vm_vec_scale(&targetPos, 0.5); vm_vec_add(&cameraPos, &cameraPos, &targetPos); } for (int i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && (Ships[i].flags2 & SF2_NAVPOINT_CARRY || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags & WF_NAV_CARRY) ) ) { vm_vec_add(&Objects[Ships[i].objnum].pos, &Objects[Ships[i].objnum].pos, &targetPos); } } // retime all collision pairs obj_all_collisions_retime(); } // ******************************************************************************************** // Checks for changes every NPS_TICKRATE milliseconds // Checks: // * if we've gotten close enough to a nav point for it to be counted as "Visited" // * If we're current AutoNavigating it checks if we need to autodisengage void NavSystem_Do() { static unsigned int last_update = 0; if (clock () - last_update > NPS_TICKRATE) { if (AutoPilotEngaged) { if (The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS) { camera *cam = nav_get_set_camera(); if (CinematicStarted) { // update our cinematic and possibly perform warp //if (!CameraMoving) if(cam != NULL) cam->set_rotation_facing(&Player_obj->pos); if (timestamp() >= MoveCamera && !CameraMoving && vm_vec_mag(&cameraTarget) > 0.0f) { //Free_camera->set_position(&cameraTarget, float(camMovingTime), float(camMovingTime)/2.0f); //Free_camera->set_translation_velocity(&cameraTarget); CameraMoving = true; } if (timestamp() >= EndAPCinematic || Autopilot_AutoDiable()) { nav_warp(); EndAutoPilot(); } } else { // start cinematic if (UseCutsceneBars) { Cutscene_bar_flags |= CUB_CUTSCENE; Cutscene_bar_flags &= ~CUB_GRADUAL; } nav_warp(true); if(cam != NULL) { cam->set_position(&cameraPos); cam->set_rotation_facing(&Player_obj->pos); } CinematicStarted = true; } } else { if (Autopilot_AutoDiable()) EndAutoPilot(); } } // check if a NavPoints target has left, delete it if so int i; for (i = 0; i < MAX_NAVPOINTS; i++) { if ((Navs[i].flags & NP_SHIP) && (Navs[i].target_obj != NULL)) { if (((ship*)Navs[i].target_obj)->objnum == -1) { if (CurrentNav == i) CurrentNav = -1; DelNavPoint(i); } } } // check if we're reached a Node for (i = 0; i < MAX_NAVPOINTS; i++) { if (Navs[i].target_obj != NULL) { if (Navs[i].flags & NP_VALIDTYPE && DistanceTo(i) < 1000) Navs[i].flags |= NP_VISITED; } } } // ramp time compression - only if not using cinematics if (AutoPilotEngaged && !(The_mission.flags & MISSION_FLAG_USE_AP_CINEMATICS)) { int dstfrm_start = start_dist - DistanceTo(CurrentNav); // Ramp UP time compression if (dstfrm_start < (3500*ramp_bias)) { if (dstfrm_start >= (3000*ramp_bias) && DistanceTo(CurrentNav) > 30000) set_time_compression(64); else if (dstfrm_start >= (2000*ramp_bias)) set_time_compression(32); else if (dstfrm_start >= (1600*ramp_bias)) set_time_compression(16); else if (dstfrm_start >= (1200*ramp_bias)) set_time_compression(8); else if (dstfrm_start >= (800*ramp_bias)) set_time_compression(4); else if (dstfrm_start >= (400*ramp_bias)) set_time_compression(2); } // Ramp DOWN time compression if (DistanceTo(CurrentNav) <= (7000*ramp_bias)) { int dist = DistanceTo(CurrentNav); if (dist >= (5000*ramp_bias)) set_time_compression(32); else if (dist >= (4000*ramp_bias)) set_time_compression(16); else if (dist >= (3000*ramp_bias)) set_time_compression(8); else if (dist >= (2000*ramp_bias)) set_time_compression(4); else if (dist >= (1000*ramp_bias)) set_time_compression(2); } } // autopilot linking for (int i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && Ships[i].flags2 & SF2_NAVPOINT_NEEDSLINK) { object *other_objp = &Objects[Ships[i].objnum]; if (vm_vec_dist_quick(&Player_obj->pos, &other_objp->pos) < (NavLinkDistance + other_objp->radius)) { Ships[i].flags2 &= ~SF2_NAVPOINT_NEEDSLINK; Ships[i].flags2 |= SF2_NAVPOINT_CARRY; send_autopilot_msgID(NP_MSG_MISC_LINKED); } } } } // ******************************************************************************************** void send_autopilot_msgID(int msgid) { if (msgid < 0 || msgid > NP_NUM_MESSAGES) return; send_autopilot_msg(NavMsgs[msgid].message, NavMsgs[msgid].filename); } // ******************************************************************************************** void send_autopilot_msg(char *msg, char *snd) { // setup if (audio_handle != -1) { audiostream_close_file(audio_handle, 0); audio_handle = -1; } if (strlen(msg) != 0 && strcmp(msg, "none")) change_message("autopilot builtin message", msg, -1, 0); // load sound if (snd != NULL || strlen(snd) == 0 || !strcmp(snd, "none")) { audio_handle = audiostream_open(snd, ASF_MENUMUSIC ); } // send/play if (audio_handle != -1) { audiostream_play(audio_handle, Master_event_music_volume, 0); } if (strlen(msg) != 0 && strcmp(msg, "none")) message_training_queue("autopilot builtin message", timestamp(0), 5); // display message for five seconds } // ******************************************************************************************** // Inits the Nav System void NavSystem_Init() { memset((char *)&Navs, 0, sizeof(Navs)); AutoPilotEngaged = false; CurrentNav = -1; audio_handle = -1; CinematicStarted = false; UseCutsceneBars = true; // defaults... can be tabled or bound to mission later if (cf_exists_full("autopilot.tbl", CF_TYPE_TABLES)) parse_autopilot_table("autopilot.tbl"); else parse_autopilot_table(NULL); } // ******************************************************************************************** void parse_autopilot_table(char *filename) { int rval; SCP_vector lines; // open localization lcl_ext_open(); if ((rval = setjmp(parse_abort)) != 0) { mprintf(("TABLES: Unable to parse '%s'! Error code = %i.\n", (filename) ? filename : "", rval)); lcl_ext_close(); return; } if (filename == NULL) read_file_text_from_array(defaults_get_file("autopilot.tbl")); else read_file_text(filename, CF_TYPE_TABLES); reset_parse(); required_string("#Autopilot"); // autopilot link distance required_string("$Link Distance:"); stuff_int(&NavLinkDistance); // optional no cutscene bars if (optional_string("+No_Cutscene_Bars")) UseCutsceneBars = false; // optional no cutscene bars if (optional_string("+No_Autopilot_Interrupt")) Cmdline_autopilot_interruptable = 0; // No Nav selected message char *msg_tags[] = { "$No Nav Selected:", "$Gliding:", "$Too Close:", "$Hostiles:", "$Linked:", "$Hazard:" }; for (int i = 0; i < NP_NUM_MESSAGES; i++) { required_string(msg_tags[i]); required_string("+Msg:"); stuff_string(NavMsgs[i].message, F_MESSAGE, 256); required_string("+Snd File:"); stuff_string(NavMsgs[i].filename, F_NAME, 256); } required_string("#END"); // close localization lcl_ext_close(); } // ******************************************************************************************** // Finds a Nav point by name int FindNav(char *Nav) { for (int i = 0; i < MAX_NAVPOINTS; i++) { if (!stricmp(Navs[i].NavName, Nav)) return i; } return -1; } // ******************************************************************************************** // Set A Nav point to "ZERO" void ZeroNav(int i) { memset((char *)&Navs[i], 0, sizeof(NavPoint)); } // ******************************************************************************************** // Removes a Nav bool DelNavPoint(char *Nav) { int n = FindNav(Nav); return DelNavPoint(n); } bool DelNavPoint(int nav) { if (nav != -1) { if (nav != MAX_NAVPOINTS-1) { for (int i = nav; i < MAX_NAVPOINTS-1; i++) { Navs[nav] = Navs[nav+1]; } } ZeroNav(MAX_NAVPOINTS-1); return true; } return false; } // ******************************************************************************************** // adds a Nav bool AddNav_Ship(char *Nav, char *TargetName, int flags) { // find an empty nav - should be the end int empty = -1; int i; for (i = 0; i < MAX_NAVPOINTS && empty == -1; i++) { if (Navs[i].flags == 0) empty = i; } if (empty == -1) // no empty navpoint slots return false; // Create the NavPoint struct NavPoint tnav; strncpy(tnav.NavName, Nav, 32); tnav.flags = NP_SHIP | flags; Assert(!(tnav.flags & NP_WAYPOINT)); for (i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && !stricmp(TargetName, Ships[i].ship_name)) { tnav.target_obj = (void *)&Ships[i]; } } tnav.waypoint_num = 0; // unused for NP_SHIP // copy it into it's location Navs[empty] = tnav; return true; } bool AddNav_Waypoint(char *Nav, char *WP_Path, int node, int flags) { // find an empty nav - should be the end int empty = -1; int i; if (node == 0) node = 1; for (i = 0; i < MAX_NAVPOINTS && empty == -1; i++) { if (Navs[i].flags == 0) empty = i; } if (empty == -1) // no empty navpoint slots return false; // Create the NavPoint struct NavPoint tnav; strncpy(tnav.NavName, Nav, 32); tnav.flags = NP_WAYPOINT | flags; Assert(!(tnav.flags & NP_SHIP)); for (i = 0; i < Num_waypoint_lists; i++) { if (!stricmp(WP_Path, Waypoint_lists[i].name)) { tnav.target_obj = (void *)&Waypoint_lists[i]; } } tnav.waypoint_num = node; // copy it into it's location Navs[empty] = tnav; return true; } // ******************************************************************************************** //Change Flags bool Nav_Alt_Flags(char *Nav, int flags) { flags &= ~NP_VALIDTYPE; //clear the NP_SHIP and NP_WAYPOINT bits, make sure they haven't been set int nav = FindNav(Nav); if (nav != -1) { Navs[nav].flags &= NP_VALIDTYPE; // Clear all bits BUT NP_SHIP or NO_WAYPOINT Navs[nav].flags |= flags; // merge } return (nav != -1); } //Get Flags int Nav_Get_Flags(char *Nav) { int flags = 0; int nav = FindNav(Nav); if (nav != -1) flags = Navs[nav].flags; return flags; } // ******************************************************************************************** // Sexp Accessors //Generic bool Nav_Set_Flag(char *Nav, int flag) { Assert(!(flag & NP_VALIDTYPE)); int nav = FindNav(Nav); if (nav != -1) { Navs[nav].flags |= flag; return true; } return false; } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_Flag(char *Nav, int flag) { Assert(!(flag & NP_VALIDTYPE)); int nav = FindNav(Nav); if (nav != -1) { Navs[nav].flags &= ~flag; return true; } return false; } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ //Named bool Nav_Set_Hidden(char *Nav) { return Nav_Set_Flag(Nav, NP_HIDDEN); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_Set_NoAccess(char *Nav) { return Nav_Set_Flag(Nav, NP_NOACCESS); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_Set_Visited(char *Nav) { return Nav_Set_Flag(Nav, NP_VISITED); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_Hidden(char *Nav) { return Nav_UnSet_Flag(Nav, NP_HIDDEN); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_NoAccess(char *Nav) { return Nav_UnSet_Flag(Nav, NP_NOACCESS); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_Visited(char *Nav) { return Nav_UnSet_Flag(Nav, NP_VISITED); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ unsigned int DistanceTo(char *nav) { int n = FindNav(nav); return DistanceTo(n); } unsigned int DistanceTo(int nav) { if (nav >= MAX_NAVPOINTS && nav < 0) return 0xFFFFFFFF; return (uint)vm_vec_dist_quick(&Player_obj->pos, Navs[nav].GetPosition()); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool IsVisited(char *nav) { int n = FindNav(nav); return IsVisited(n); } bool IsVisited(int nav) { if (nav >= MAX_NAVPOINTS && nav < 0) return 0; if (Navs[nav].flags & NP_VISITED) return 1; return 0; }