mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
1611 lines
45 KiB
C++
1611 lines
45 KiB
C++
// 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 <map>
|
|
|
|
// 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<int,int> 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; n<MAX_ASTEROIDS; n++)
|
|
{
|
|
// asteroid
|
|
if (Asteroids[n].flags & AF_USED)
|
|
{
|
|
// Cannot autopilot if asteroid within AutopilotMinAsteroidDistance meters
|
|
if (vm_vec_dist_quick(targetPos, &Objects[Asteroids[n].objnum].pos) < AutopilotMinAsteroidDistance) {
|
|
if (send_msg)
|
|
send_autopilot_msgID(NP_MSG_FAIL_HAZARD);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for support ships
|
|
// cannot autopilot if support ship present
|
|
if ( ship_find_repair_ship(Player_obj) != 0 ) {
|
|
if (send_msg)
|
|
send_autopilot_msgID(NP_MSG_FAIL_SUPPORT_PRESENT);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
extern object* Autopilot_flight_leader;
|
|
// ********************************************************************************************
|
|
// Engages autopilot
|
|
// This does:
|
|
// * Control switched from player to AI
|
|
// * Time compression to 32x
|
|
// * Lock time compression -WMC
|
|
// * Tell AI to fly to targeted Nav Point (for all nav-status wings/ships)
|
|
// * Sets max waypoint speed to the best-speed of the slowest ship tagged
|
|
bool StartAutopilot()
|
|
{
|
|
// Check for support ship and dismiss it if it is not doing anything.
|
|
// If the support ship is doing something then tell the user such.
|
|
for (auto so: list_range(&Ship_obj_list))
|
|
{
|
|
auto objp = &Objects[so->objnum];
|
|
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<int> 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<int>::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<SCP_string> 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 : "<default autopilot.tbl>", 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;
|
|
}
|
|
} |