// Autopilot.cpp // Derek Meek // 4-30-2004 #include "ai/ai.h" #include "ai/aigoals.h" #include "asteroid/asteroid.h" #include "autopilot/autopilot.h" #include "camera/camera.h" #include "cfile/cfile.h" #include "freespace.h" #include "gamesnd/eventmusic.h" #include "def_files/def_files.h" #include "globalincs/linklist.h" #include "iff_defs/iff_defs.h" #include "io/timer.h" #include "localization/localize.h" #include "mission/missionmessage.h" #include "mission/missiontraining.h" #include "object/objcollide.h" #include "object/waypoint.h" #include "parse/parselo.h" #include "parse/sexp.h" #include "ship/ship.h" #include "sound/audiostr.h" #include // Extern functions/variables extern bool 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; bool LockWeaponsDuringAutopilot; int CurrentNav; float ramp_bias; NavPoint Navs[MAX_NAVPOINTS]; NavMessage NavMsgs[NP_NUM_MESSAGES]; int audio_handle; int NavLinkDistance; // time offsets for autonav events TIMESTAMP LockAPConv; TIMESTAMP EndAPCinematic; TIMESTAMP MoveCamera; //float CameraSpeed; bool CinematicStarted, CameraMoving; vec3d cameraPos, cameraTarget; SCP_map autopilot_wings; int AutopilotMinEnemyDistance; int AutopilotMinAsteroidDistance; // used for ramping time compression; int start_dist; // ******************************************************************************************** void autopilot_ai_waypoint_goal_fixup(ai_goal* aigp) { // this function sets wp_index properly; aigp->wp_list_index = find_matching_waypoint_list_index(aigp->target_name); } // ******************************************************************************************** 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; } // ******************************************************************************************** const vec3d *NavPoint::GetPosition() { if (flags & NP_WAYPOINT) { auto wpt = find_waypoint_at_indexes(target_index, waypoint_num-1); Assert(wpt != nullptr); if (wpt == nullptr) return &vmd_zero_vector; return wpt->get_pos(); } else { return &Objects[target_index].pos; } } // ******************************************************************************************** bool CanAutopilot(const vec3d *targetPos, 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(targetPos, Navs[CurrentNav].GetPosition()) < 1000) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_TOCLOSE); return false; } if ( AutopilotMinEnemyDistance > 0 ) { // 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]; if (other_objp->flags[Object::Object_Flags::Should_be_dead]) continue; // 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[Ship::Info_Flags::Cargo])) // ignore cargo { // Cannot autopilot if enemy within AutopilotMinEnemyDistance meters if (vm_vec_dist_quick(targetPos, &other_objp->pos) < AutopilotMinEnemyDistance) { if (send_msg) send_autopilot_msgID(NP_MSG_FAIL_HOSTILES); return false; } } } } if ( AutopilotMinAsteroidDistance > 0 ) { //check for asteroids for (int n=0; nobjnum]; if ((objp->type == OBJ_SHIP) && !(objp->flags[Object::Object_Flags::Should_be_dead])) { Assertion((objp->instance >= 0) && (objp->instance < MAX_SHIPS), "objp does not have a valid pointer to a ship. Pointer is %d, which is smaller than 0 or bigger than %d", objp->instance, MAX_SHIPS); ship *shipp = &Ships[objp->instance]; if (shipp->team != Player_ship->team) continue; Assertion((shipp->ship_info_index >= 0) && (shipp->ship_info_index < ship_info_size()), "Ship '%s' does not have a valid pointer to a ship class. Pointer is %d, which is smaller than 0 or bigger than %d", shipp->ship_name, shipp->ship_info_index, ship_info_size()); ship_info *sip = &Ship_info[shipp->ship_info_index]; if ( !(sip->flags[Ship::Info_Flags::Support]) ) continue; // don't deal with dying or departing support ships if ( shipp->is_dying_or_departing() ) continue; Assert(shipp->ai_index != -1); ai_info* support_ship_aip = &(Ai_info[Ships[objp->instance].ai_index]); // is support ship trying to rearm-repair if ( ai_find_goal_index( support_ship_aip->goals, AI_GOAL_REARM_REPAIR ) == -1 ) { // no, so tell it to depart ai_add_ship_goal_player( ai_goal_type::PLAYER_SHIP, AI_GOAL_WARP, -1, nullptr, support_ship_aip ); } else { // yes send_autopilot_msgID(NP_MSG_FAIL_SUPPORT_WORKING); return false; } } } if (!CanAutopilot()) return false; AutoPilotEngaged = true; // find the ship that is "leading" all of the ships when the player starts // autopilot // by default the ship that is leading the autopilot session the player's // wing leader (if the player is the wing leader then it will be the // player). // TODO:implement a way to allow a FREDer to say a different ship is leader Autopilot_flight_leader = get_wing_leader(Player_ship->wingnum); if ( Autopilot_flight_leader == NULL ) { // force player to be the leader if he doesn't have a wing Autopilot_flight_leader = Player_obj; } if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics]) LockAPConv = _timestamp(); // lock convergence instantly else LockAPConv = _timestamp(3 * MILLISECONDS_PER_SECOND); // 3 seconds before we lock convergence Player_use_ai = true; set_time_compression(1); lock_time_compression(true); // determine speed cap int i,j, wcount=1, tc_factor=1; float speed_cap = 1000000.0; // 1m is a safe starting point float radius = Player_obj->radius, 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, perp, tpos, rpos = Player_obj->pos, zero; memset(&zero, 0, sizeof(vec3d)); // instantly turn player toward tpos if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics]) { vec3d norm1; vm_vec_normalized_dir(&norm1, Navs[CurrentNav].GetPosition(), &Player_obj->pos); vm_vector_2_matrix_norm(&Player_obj->orient, &norm1, nullptr, nullptr); } for (i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && (Ships[i].flags[Ship::Ship_Flags::Navpoint_carry] || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags[Ship::Wing_Flags::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::Mission_Flags::Use_ap_cinematics]) speed_cap = 0.90f * speed_cap; if ( speed_cap < 1.0f ) { /* We need to deal with this so that incorrectly flagged ships will not cause the engine to fail to limit all the ships speeds correctly. */ Warning(LOCATION, "Ship speed cap is way too small (%f)!\n" "This is normally caused by a ship that has nav-carry-status set, but cannot move itself (like a Cargo container).\n" "Speed cap has been set to 1.0 m/s.", speed_cap); speed_cap = 1.0f; } 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].flags[Ship::Ship_Flags::Navpoint_carry] || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags[Ship::Wing_Flags::Nav_carry]) ) ) { // do we have capital ships in the area? if (Ship_info[Ships[i].ship_info_index].is_big_ship() || Ship_info[Ships[i].ship_info_index].flags[Ship::Info_Flags::Capital] || Ship_info[Ships[i].ship_info_index].flags[Ship::Info_Flags::Supercap]) { capshipPresent = true; capIndexes.push_back(i); // ok.. what size class if (Ship_info[Ships[i].ship_info_index].flags[Ship::Info_Flags::Capital] || Ship_info[Ships[i].ship_info_index].flags[Ship::Info_Flags::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[Ship::Info_Flags::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; } // instantly turn the ship to match the direction player is looking if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics]) { vec3d norm1; vm_vec_normalized_dir(&norm1, Navs[CurrentNav].GetPosition(), &Player_obj->pos); vm_vector_2_matrix_norm(&Objects[Ships[i].objnum].orient, &norm1, nullptr, nullptr); } // snap wings into formation if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics] && // only if using cinematics (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags[Ship::Wing_Flags::Nav_carry]) // only if in a wing && Autopilot_flight_leader != &Objects[Ships[i].objnum]) //only if not flight leader's object { ai_info *aip = &Ai_info[Ships[i].ai_index]; int wingnum = Ships[i].wingnum, 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(&goal_point, leader_objp, wingnum, wing_index, aip->ai_flags[AI::AI_Flags::Formation_object], true); } else { ai_clear_wing_goals(&Wings[wingnum]); j = 1+int( (float)floor(double(wcount-1)/2.0) ); switch (wcount % 2) { case 1: // back-left vm_vec_add(&perp, &zero, &Autopilot_flight_leader->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, &Autopilot_flight_leader->pos, &perp); break; default: //back-right case 0: vm_vec_add(&perp, &zero, &Autopilot_flight_leader->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, &Autopilot_flight_leader->pos, &perp); break; } autopilot_wings[wingnum] = wcount; wcount++; } Objects[Ships[i].objnum].pos = goal_point; if (vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[i].objnum].pos) > distance) { distance = vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[i].objnum].pos); } } // lock primary and secondary weapons if (LockWeaponsDuringAutopilot) { Ships[i].flags.set(Ship::Ship_Flags::Primaries_locked); Ships[i].flags.set(Ship::Ship_Flags::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::Mission_Flags::Use_ap_cinematics]) { if (Navs[CurrentNav].flags & NP_WAYPOINT) { ai_add_ship_goal_player( ai_goal_type::PLAYER_SHIP, AI_GOAL_WAYPOINTS_ONCE, 0, Waypoint_lists[Navs[CurrentNav].target_index].get_name(), &Ai_info[Ships[i].ai_index] ); //fixup has to wait until after wing goals } else { ai_add_ship_goal_player( ai_goal_type::PLAYER_SHIP, AI_GOAL_FLY_TO_SHIP, 0, Ships[Objects[Navs[CurrentNav].target_index].instance].ship_name, &Ai_info[Ships[i].ai_index] ); } } } } // assign wing goals if (!(The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics])) { for (i = 0; i < MAX_WINGS; i++) { if (Wings[i].flags[Ship::Wing_Flags::Nav_carry] ) { //ai_add_ship_goal_player( int type, int mode, int submode, char *shipname, ai_info *aip ); //ai_add_wing_goal_player( ai_goal_type::PLAYER_WING, AI_GOAL_STAY_NEAR_SHIP, 0, target_shipname, wingnum ); //ai_add_wing_goal_player( ai_goal_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( &Wings[i] ); if (Navs[CurrentNav].flags & NP_WAYPOINT) { ai_add_wing_goal_player( ai_goal_type::PLAYER_WING, AI_GOAL_WAYPOINTS_ONCE, 0, Waypoint_lists[Navs[CurrentNav].target_index].get_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( ai_goal_type::PLAYER_WING, AI_GOAL_FLY_TO_SHIP, 0, Ships[Objects[Navs[CurrentNav].target_index].instance].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].flags[Ship::Ship_Flags::Navpoint_carry] || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags[Ship::Wing_Flags::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::Mission_Flags::Use_ap_cinematics]) { if (capshipPresent) { // position capships vec3d right, front, up, offset; for (SCP_vector::iterator idx = capIndexes.begin(); idx != capIndexes.end(); ++idx) { vm_vec_add(&right, &Autopilot_flight_leader->orient.vec.rvec, &zero); vm_vec_add(&front, &Autopilot_flight_leader->orient.vec.fvec, &zero); vm_vec_add(&up, &Autopilot_flight_leader->orient.vec.uvec, &zero); vm_vec_add(&offset, &zero, &zero); if (Ship_info[Ships[*idx].ship_info_index].flags[Ship::Info_Flags::Capital] || Ship_info[Ships[*idx].ship_info_index].flags[Ship::Info_Flags::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[*idx].ship_info_index].flags[Ship::Info_Flags::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, &Autopilot_flight_leader->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, &Autopilot_flight_leader->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[*idx].objnum].pos, &Autopilot_flight_leader->pos, &offset); if (vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[*idx].objnum].pos) > distance) { distance = vm_vec_dist_quick(&Autopilot_flight_leader->pos, &Objects[Ships[*idx].objnum].pos); } } } ftemp = floor(Autopilot_flight_leader->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, &Autopilot_flight_leader->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, &Autopilot_flight_leader->pos); // pos is now 5*speed cap in front of player ship switch (Random::next(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, &Autopilot_flight_leader->orient.vec.uvec); else { // become up-left vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.uvec); vm_vec_sub(&perp, &perp, &Autopilot_flight_leader->orient.vec.rvec); } break; case 2: // up case 10: case 23: vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); if (capshipPresent) // become up-right vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.rvec); break; case 3: // left case 11: case 22: vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec); break; case 4: // up-left case 12: case 21: vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec); vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); break; case 5: // up-right case 13: case 20: vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec); vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); break; case 6: // down-left case 14: case 19: vm_vec_sub(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec); if (capship_placed[0] < 2) vm_vec_sub(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); else vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); break; case 7: // down-right case 15: case 18: vm_vec_add(&perp, &zero, &Autopilot_flight_leader->orient.vec.rvec); if (capship_placed[0] < 1) vm_vec_sub(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); else vm_vec_add(&perp, &perp, &Autopilot_flight_leader->orient.vec.uvec); break; default: case 0: // right case 8: case 17: perp = Autopilot_flight_leader->orient.vec.rvec; break; } vm_vec_normalize(&perp); //vm_vec_scale(&perp, 2*radius+distance); vm_vec_scale(&perp, Autopilot_flight_leader->radius+radius); // randomly scale up/down by up to 20% // jg18 - original comment said [-20,20] but preserving existing behavior j = Random::next(-19, 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((10 * MILLISECONDS_PER_SECOND * tc_factor) + NPS_TICKRATE); // 10 objective seconds before end of cinematic MoveCamera = _timestamp(fl2i(5.5 * MILLISECONDS_PER_SECOND * tc_factor) + NPS_TICKRATE); set_time_compression((float)tc_factor); } return true; } // ******************************************************************************************** // Disengages autopilot // this does: // * Time compression to 1x // * Delete AI nav goal // * Control switched from AI to player void EndAutoPilot() { AutoPilotEngaged = false; Autopilot_flight_leader = NULL; set_time_compression(1); lock_time_compression(false); Player_use_ai = false; //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; const char *goal_name = NULL; if (Navs[CurrentNav].flags & NP_WAYPOINT) { goal = AI_GOAL_WAYPOINTS_ONCE; goal_name = Waypoint_lists[Navs[CurrentNav].target_index].get_name(); } else { goal = AI_GOAL_FLY_TO_SHIP; goal_name = Ships[Objects[Navs[CurrentNav].target_index].instance].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].flags[Ship::Ship_Flags::Navpoint_carry] || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags[Ship::Wing_Flags::Nav_carry] ) ) ) { //unlock their weaponry if (LockWeaponsDuringAutopilot) { Ships[i].flags.remove(Ship::Ship_Flags::Primaries_locked); Ships[i].flags.remove(Ship::Ship_Flags::Secondaries_locked); } Ai_info[Ships[i].ai_index].waypoint_speed_cap = -1; // uncap their speed Ai_info[Ships[i].ai_index].mode = AIM_NONE; // make AI re-evaluate current ship mode for (j = 0; j < MAX_AI_GOALS; j++) { ai_goal *aigp = &Ai_info[Ships[i].ai_index].goals[j]; if ( ((aigp->target_name != NULL) && !stricmp(aigp->target_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->target_name != NULL) && !stricmp(aigp->target_name, goal_name)) && (aigp->ai_mode == goal) ) { ai_goal_reset(aigp); } } } } } // un-assign wing goals if ( !(The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics]) ) { for (i = 0; i < MAX_WINGS; i++) { if (Wings[i].flags[Ship::Wing_Flags::Nav_carry] ) { for (j = 0; j < MAX_AI_GOALS; j++) { ai_goal *aigp = &Wings[i].ai_goals[j]; if ( ((aigp->target_name != NULL) && !stricmp(aigp->target_name, goal_name)) && (aigp->ai_mode == goal) ) { ai_goal_reset(aigp); } } } } } } // ******************************************************************************************** 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 flight leader to the navpoint */ vec3d targetPos, tpos=Autopilot_flight_leader->pos, pos, velocity; /* calculate a vector that we can use to make a path from the flight leader's location to the nav point */ vm_vec_sub(&pos, Navs[CurrentNav].GetPosition(), &Autopilot_flight_leader->pos); vm_vec_normalize(&pos); velocity = pos; // make a copy for later when we do setup veleocity vector vm_vec_scale(&pos, 250.0f); // we move by increments of 250 /* using the vector of the flight leaders's path, simulate moving the flight along this path by checking the autopilot conditions as specific intervals along the path*/ while (CanAutopilot(&tpos)) { vm_vec_add(&tpos, &tpos, &pos); } vm_vec_sub(&targetPos, &tpos, &Autopilot_flight_leader->pos); /* targetPos is actually a vector that describes the exact 3D movement that the flgith leader needs to execute to reach the location that the auto pilot is to shut off */ // Check if we are actually just setting up for the cinimatic shot of the // flight flying on autopilot. Only jump halfway. Also we also need to // put the camera in the correct position to show the player this cinimatic if (prewarp) { vm_vec_scale(&targetPos, 0.5); vm_vec_add(&cameraPos, &cameraPos, &targetPos); } /* calcuate the speed that everyone is supposed to be going so that there is no need for anyone to accelerate or decelerate (most obvious with the player's fighter slowing down as it changes the camera pan speed). */ Assert( Ai_info[Ships[Autopilot_flight_leader->instance].ai_index].waypoint_speed_cap > 0 ); vm_vec_scale(&velocity, (float)Ai_info[Ships[Autopilot_flight_leader->instance].ai_index].waypoint_speed_cap); // Find all ships that are supposed to autopilot with the player and move them // to the cinematic location or the final destination for (int i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && (Ships[i].flags[Ship::Ship_Flags::Navpoint_carry] || (Ships[i].wingnum != -1 && Wings[Ships[i].wingnum].flags[Ship::Wing_Flags::Nav_carry]))) { vm_vec_add(&Objects[Ships[i].objnum].pos, &Objects[Ships[i].objnum].pos, &targetPos); Objects[Ships[i].objnum].phys_info.vel = velocity; // retime collision pairs if (Objects[Ships[i].objnum].flags[Object::Object_Flags::Collides]) obj_collide_obj_cache_stale(&Objects[Ships[i].objnum]); } } } // ******************************************************************************************** // 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::Mission_Flags::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_elapsed(MoveCamera) && !CameraMoving && vm_vec_mag(&cameraTarget) > 0.0f) { CameraMoving = true; } if (timestamp_elapsed(EndAPCinematic) || !CanAutopilot()) { 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(&Autopilot_flight_leader->pos); } CinematicStarted = true; } } else { if (!CanAutopilot()) 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_index >= 0)) { if (Objects[Navs[i].target_index].flags[Object::Object_Flags::Should_be_dead]) { 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_index >= 0) { 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::Mission_Flags::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); } } /* Link in any ships that need to be linked in when the player gets close enough. Always done by the player because: "making sure the AI fighters are where they're supposed to be at all times is like herding cats" -Woolie Wool */ for (int i = 0; i < MAX_SHIPS; i++) { if (Ships[i].objnum != -1 && Ships[i].flags[Ship::Ship_Flags::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].flags.remove(Ship::Ship_Flags::Navpoint_needslink); Ships[i].flags.set(Ship::Ship_Flags::Navpoint_carry); send_autopilot_msgID(NP_MSG_MISC_LINKED); } } } } // ******************************************************************************************** void send_autopilot_msgID(int msgid) { if (msgid < 0 || msgid >= NP_NUM_MESSAGES) return; // bail out if message is not set if (NavMsgs[msgid].message[0] == '\0') return; send_autopilot_msg(NavMsgs[msgid].message, NavMsgs[msgid].filename); } // ******************************************************************************************** void send_autopilot_msg(const char *msg, const char *snd) { // setup if (audio_handle != -1) { audiostream_close_file(audio_handle, 0); audio_handle = -1; } if (msg[0] != '\0' && strcmp(msg, "none") != 0) change_message("autopilot builtin message", msg, -1, 0); // load sound if ((snd != NULL) && (snd[0] != '\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 * aav_music_volume), 0); } if (msg[0] != '\0' && strcmp(msg, "none") != 0) message_training_queue("autopilot builtin message", TIMESTAMP::immediate(), 5); // display message for five seconds } // ******************************************************************************************** // Inits the Nav System void NavSystem_Init() { for (int i = 0; i < MAX_NAVPOINTS; i++) Navs[i].clear(); //Initialize all these as empty strings so we can detect and bail out later -Mjn for (int i = 0; i < NP_NUM_MESSAGES; i++) NavMsgs[i].message[0] = '\0'; //Set defaults here before parsing NavLinkDistance = 1000; AutopilotMinEnemyDistance = 5000; AutopilotMinAsteroidDistance = 1000; LockWeaponsDuringAutopilot = false; AutoPilotEngaged = false; CurrentNav = -1; audio_handle = -1; CinematicStarted = false; UseCutsceneBars = true; // defaults... can be tabled or bound to mission later parse_autopilot_table("autopilot.tbl"); parse_modular_table("*-aplt.tbm", parse_autopilot_table); } // ******************************************************************************************** void parse_autopilot_table(const char *filename) { SCP_vector lines; try { if (!Parsing_modular_table) { // if table doesn't exist, use the default table if (cf_exists_full(filename, CF_TYPE_TABLES)) read_file_text(filename, CF_TYPE_TABLES); else read_file_text_from_default(defaults_get_file("autopilot.tbl")); } else { read_file_text(filename, CF_TYPE_TABLES); } reset_parse(); required_string("#Autopilot"); // autopilot link distance if (optional_string("$Link Distance:")) stuff_int(&NavLinkDistance); if (optional_string("$Interrupt autopilot if enemy within distance:")) stuff_int(&AutopilotMinEnemyDistance); if (optional_string("$Interrupt autopilot if asteroid within distance:")) stuff_int(&AutopilotMinAsteroidDistance); if (optional_string("$Lock Weapons During Autopilot:")) stuff_boolean(&LockWeaponsDuringAutopilot); // 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 const char *msg_tags[] = { "$No Nav Selected:", "$Gliding:", "$Too Close:", "$Hostiles:", "$Linked:", "$Hazard:", "$Support Present:", "$Support Working:" }; for (int i = 0; i < NP_NUM_MESSAGES; i++) { if (optional_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"); } catch (const parse::ParseException& e) { mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", (filename) ? filename : "", e.what())); return; } } // ******************************************************************************************** // Finds a Nav point by name int FindNav(const char *Nav) { for (int i = 0; i < MAX_NAVPOINTS; i++) { if (!stricmp(Navs[i].m_NavName, Nav)) return i; } return -1; } // ******************************************************************************************** // Removes a Nav bool DelNavPoint(const 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]; } } Navs[MAX_NAVPOINTS-1].clear(); return true; } return false; } // ******************************************************************************************** // adds a Nav bool AddNav_Ship(const char *Nav, const 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; strcpy_s(tnav.m_NavName, Nav); 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_index = Ships[i].objnum; } } tnav.waypoint_num = 0; // unused for NP_SHIP // copy it into it's location Navs[empty] = tnav; return true; } bool AddNav_Waypoint(const char *Nav, const 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; strcpy_s(tnav.m_NavName, Nav); tnav.flags = NP_WAYPOINT | flags; Assert(!(tnav.flags & NP_SHIP)); tnav.target_index = find_matching_waypoint_list_index(WP_Path); tnav.waypoint_num = node; // copy it into it's location Navs[empty] = tnav; return true; } // ******************************************************************************************** //Get Flags int Nav_Get_Flags(const 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(const 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(const 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(const char *Nav) { return Nav_Set_Flag(Nav, NP_HIDDEN); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_Set_NoAccess(const char *Nav) { return Nav_Set_Flag(Nav, NP_NOACCESS); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_Set_Visited(const char *Nav) { return Nav_Set_Flag(Nav, NP_VISITED); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_Hidden(const char *Nav) { return Nav_UnSet_Flag(Nav, NP_HIDDEN); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_NoAccess(const char *Nav) { return Nav_UnSet_Flag(Nav, NP_NOACCESS); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ bool Nav_UnSet_Visited(const char *Nav) { return Nav_UnSet_Flag(Nav, NP_VISITED); } // ******************************************************************************************** // Selects a navpoint by name. void SelectNav(const char *Nav) { for (int i = 0; i < MAX_NAVPOINTS; i++) { if (!stricmp(Navs[i].m_NavName, Nav)) if (!(Nav_Get_Flags(Nav) & NP_HIDDEN || Nav_Get_Flags(Nav) & NP_NOACCESS)) CurrentNav = i; } } // ******************************************************************************************** // Deselects any navpoint selected. void DeselectNav() { CurrentNav = 0; } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ unsigned int DistanceTo(const 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(const 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; } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ void Nav_SetColor(const char *nav, bool visited, ubyte r, ubyte g, ubyte b) { int n = FindNav(nav); if (n >= 0 && n < MAX_NAVPOINTS) { auto rgb = visited ? Navs[n].visited_color : Navs[n].normal_color; rgb[0] = r; rgb[1] = g; rgb[2] = b; } }