mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2055 lines
74 KiB
C++
2055 lines
74 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#include "ai/aibig.h"
|
|
#include "ai/aigoals.h"
|
|
#include "freespace.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "iff_defs/iff_defs.h"
|
|
#include "io/timer.h"
|
|
#include "math/staticrand.h"
|
|
#include "mission/missionparse.h"
|
|
#include "object/objcollide.h"
|
|
#include "object/object.h"
|
|
#include "ship/afterburner.h"
|
|
#include "ship/ship.h"
|
|
#include "utils/Random.h"
|
|
#include "weapon/weapon.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma optimize("", off)
|
|
#pragma auto_inline(off)
|
|
#endif
|
|
|
|
#define SCAN_FIGHTERS_INTERVAL 2000 // how often an AI fighter/bomber should scan for enemy fighter/bombers
|
|
// if sitting still and pounding on a big ship. If enemy fighters are
|
|
// close ( < ENTER_STRAFE_THREAT_DIST ), then enter AIM_STRAFE
|
|
|
|
#define ENTER_STRAFE_THREAT_DIST_SQUARED 360000 // use squared distance, instead of 600
|
|
|
|
#define MIN_DOT_TO_ATTACK_SUBSYS 0.7f
|
|
#define MIN_DOT_TO_ATTACK_MOVING_SUBSYS 0.97f
|
|
|
|
// AI BIG MAGIC NUMBERS
|
|
// Select strafing options are now exposed to modders --wookieejedi
|
|
#define STRAFE_RETREAT_COLLIDE_TIME 2.0 // when anticipated collision time is less than this, begin retreat
|
|
#define STRAFE_RETREAT_COLLIDE_DIST 100 // when perpendicular distance to *surface* is less than this, begin retreat
|
|
|
|
#define EVADE_BOX_BASE_DISTANCE 300 // standard distance to end evade submode
|
|
#define EVADE_BOX_MIN_DISTANCE 200 // minimun distance to end evade submode, after long time
|
|
|
|
#define ATTACK_STOP_DISTANCE 150 // when distance to target is less than this, put on brakes
|
|
|
|
#define ATTACK_COLLIDE_BASE_DIST 300 // absolute distance at which to begin checking for possible collision
|
|
#define ATTACK_COLLIDE_AVOID_DIST 60 // perpendicular distance to attack surface at which begin avoiding
|
|
#define ATTACK_COLLIDE_AVOID_TIME 1.0 // anticipated collision time at which to begin evade
|
|
#define ATTACK_COLLIDE_SLOW_DIST 150 // perpendicular distance to attack surface at which begin slowing down
|
|
#define ATTACK_COLLIDE_SLOW_TIME 1.5 // anticipated collision time at which to begin slowing down
|
|
|
|
#define GLIDE_STRAFE_DISTANCE 50.0f //Distance from the ship to pass when glide strafing
|
|
#define GLIDE_STRAFE_MIN_TIME 2 //Minimum amount of time to stay on one glide strafe approach vector
|
|
#define GLIDE_STRAFE_MAX_TIME 15 //Maximum amount of time for each glide strafe pass
|
|
|
|
// forward declarations
|
|
void ai_big_evade_ship();
|
|
void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float dist_to_enemy, int modelnum);
|
|
void ai_big_avoid_ship();
|
|
bool ai_big_maybe_follow_subsys_path(bool do_dot_check=true);
|
|
void ai_big_strafe_position();
|
|
static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos);
|
|
|
|
extern void compute_desired_rvec(vec3d *rvec, const vec3d *goal_pos, const vec3d *cur_pos);
|
|
extern void big_ship_collide_recover_start(const object *objp, const object *big_objp, const vec3d *collision_normal);
|
|
|
|
|
|
// Called by ai_big_pick_attack_point.
|
|
// Generates a random attack point.
|
|
// If truly_random flag set (haha), then generate a pretty random number. Otherwise, generate a static rand which
|
|
// tends to not change from frame to frame.
|
|
// Try four times and choose nearest point to increase chance of getting a good point.
|
|
void ai_bpap(const object *objp, const vec3d *attacker_objp_pos, const vec3d *attacker_objp_fvec, vec3d *attack_point, vec3d *local_attack_point, float fov, float weapon_travel_dist, vec3d *surface_normal, const ship_subsys *ss)
|
|
{
|
|
float nearest_dist;
|
|
vec3d result_point, best_point;
|
|
vec3d rel_point;
|
|
int num_tries;
|
|
int i;
|
|
ship_info *sip = &Ship_info[Ships[objp->instance].ship_info_index];
|
|
polymodel* pm = model_get(sip->model_num);
|
|
model_subsystem *tp = NULL;
|
|
if (ss != NULL)
|
|
tp = ss->system_info;
|
|
|
|
best_point = objp->pos;
|
|
nearest_dist = weapon_travel_dist;
|
|
|
|
// attempt anywhere from 16 to 4 times, more if we're closer
|
|
num_tries = (int) (vm_vec_dist(&objp->pos, attacker_objp_pos)/objp->radius);
|
|
if (num_tries >= 4)
|
|
num_tries = 1;
|
|
else
|
|
num_tries = (4 - num_tries) * 4;
|
|
|
|
vm_vec_zero(local_attack_point);
|
|
|
|
auto collision_tree = model_get_bsp_collision_tree(pm->submodel[pm->detail[0]].collision_tree_index);
|
|
|
|
if (!collision_tree->poly_centers.empty()) {
|
|
int num_polies = (int)collision_tree->poly_centers.size();
|
|
|
|
if (num_tries > num_polies)
|
|
num_tries = num_polies;
|
|
|
|
for (i=0; i<num_tries; i++) {
|
|
int index;
|
|
float dist, dot;
|
|
vec3d v2p;
|
|
|
|
index = (int) (frand() * num_polies);
|
|
|
|
rel_point = collision_tree->poly_centers[index];
|
|
vm_vec_unrotate(&result_point, &rel_point, &objp->orient);
|
|
vm_vec_add2(&result_point, &objp->pos);
|
|
|
|
dist = vm_vec_normalized_dir(&v2p, &result_point, attacker_objp_pos);
|
|
bool in_fov = false;
|
|
|
|
dot = vm_vec_dot(&v2p, attacker_objp_fvec);
|
|
if (tp == NULL) {
|
|
if (dot > fov)
|
|
in_fov = true;
|
|
} else {
|
|
in_fov = turret_fov_test(ss, attacker_objp_fvec, &v2p);
|
|
}
|
|
|
|
if (in_fov) {
|
|
if (dist < nearest_dist) {
|
|
nearest_dist = dist;
|
|
best_point = result_point;
|
|
*local_attack_point = rel_point;
|
|
Assert( !vm_is_vec_nan(local_attack_point) );
|
|
if (dot > (1.0f + fov)/2.0f) // If this point is quite good, quit searching for a better one.
|
|
goto done_1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
done_1:
|
|
|
|
*attack_point = best_point;
|
|
|
|
//The following raycast tends to make up 10%(!) of total AI-frametime for basically no benefit, especially in the common case for turrets where the normal isn't even queried.
|
|
if (surface_normal == nullptr && Disable_expensive_turret_target_check)
|
|
return;
|
|
|
|
// Cast from attack_objp_pos to local_attack_pos and check for nearest collision.
|
|
// If no collision, cast to (0,0,0) [center of big ship]** [best_point initialized to 000]
|
|
|
|
// do in world coords to get attack point, then translate to local for local_attack_point
|
|
vec3d attack_dir, end_point, temp;
|
|
float dist;
|
|
dist = vm_vec_normalized_dir(&attack_dir, attack_point, attacker_objp_pos);
|
|
|
|
if (dist > 0.1) {
|
|
vm_vec_scale_add(&end_point, attack_point, &attack_dir, 30.0f);
|
|
} else {
|
|
vm_vec_scale_add(&end_point, attack_point, attacker_objp_fvec, 30.0f);
|
|
}
|
|
|
|
mc_info mc;
|
|
mc.model_instance_num = Ships[objp->instance].model_instance_num;
|
|
mc.model_num = sip->model_num;
|
|
mc.orient = &objp->orient;
|
|
mc.pos = &objp->pos;
|
|
mc.p0 = attacker_objp_pos;
|
|
mc.p1 = &end_point;
|
|
mc.flags = MC_CHECK_MODEL;
|
|
mc.radius = 0.0f;
|
|
model_collide(&mc);
|
|
|
|
if (mc.num_hits > 0) {
|
|
*attack_point = mc.hit_point_world;
|
|
vm_vec_sub(&temp, attack_point, &objp->pos);
|
|
vm_vec_rotate(local_attack_point, &temp, &objp->orient);
|
|
if (surface_normal) {
|
|
vm_vec_unrotate(surface_normal, &mc.hit_normal, &objp->orient);
|
|
}
|
|
} else {
|
|
vm_vec_zero(local_attack_point);
|
|
*attack_point = objp->pos;
|
|
if (surface_normal) {
|
|
vm_vec_zero(surface_normal);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stuff a point to attack based on nearest octant.
|
|
// If no points in that octant, leave attack_point unmodified.
|
|
//
|
|
// Note: Default value for fov is 1.0f 1.0f means don't use fov parameter.
|
|
// If fov != 1.0f, try up to four times to find a point that's in the field of view.
|
|
void ai_big_pick_attack_point_turret(const object *objp, ship_subsys *ssp, const vec3d *gpos, const vec3d *gvec, vec3d *attack_point, float fov, float weapon_travel_dist)
|
|
{
|
|
if (!timestamp_elapsed(ssp->turret_pick_big_attack_point_timestamp)) {
|
|
vec3d result_point;
|
|
vm_vec_unrotate(&result_point, &ssp->turret_big_attack_point, &objp->orient);
|
|
vm_vec_add(attack_point, &result_point, &objp->pos);
|
|
} else {
|
|
vec3d local_attack_point;
|
|
ssp->turret_pick_big_attack_point_timestamp = timestamp(2000 + (int) (frand()*500.0f));
|
|
ai_bpap(objp, gpos, gvec, attack_point, &local_attack_point, fov, weapon_travel_dist, NULL, ssp);
|
|
ssp->turret_big_attack_point = local_attack_point;
|
|
}
|
|
}
|
|
|
|
// Stuff a point to attack based on nearest octant.
|
|
// If no points in that octant, leave attack_point unmodified.
|
|
//
|
|
// Note: Default value for fov is 1.0f 1.0f means don't use fov parameter.
|
|
// If fov != 1.0f, try up to four times to find a point that's in the field of view.
|
|
// Note, attacker_objp can be a ship or a weapon.
|
|
void ai_big_pick_attack_point(const object *objp, const object *attacker_objp, vec3d *attack_point, float fov)
|
|
{
|
|
Assert(objp->instance > -1);
|
|
Assert(objp->type == OBJ_SHIP);
|
|
|
|
vec3d local_attack_point;
|
|
|
|
switch (attacker_objp->type) {
|
|
case OBJ_SHIP: {
|
|
ai_info *attacker_aip;
|
|
attacker_aip = &Ai_info[Ships[attacker_objp->instance].ai_index];
|
|
if (!timestamp_elapsed(attacker_aip->pick_big_attack_point_timestamp)) {
|
|
vec3d result_point;
|
|
|
|
vm_vec_unrotate(&result_point, &attacker_aip->big_attack_point, &objp->orient);
|
|
vm_vec_add(attack_point, &result_point, &objp->pos);
|
|
|
|
return;
|
|
}
|
|
|
|
attacker_aip->pick_big_attack_point_timestamp = timestamp(2000 + (int) (frand()*500.0f));
|
|
break;
|
|
}
|
|
case OBJ_WEAPON: {
|
|
weapon *wp = &Weapons[attacker_objp->instance];
|
|
|
|
if (!timestamp_elapsed(wp->pick_big_attack_point_timestamp)) {
|
|
vec3d result_point;
|
|
|
|
vm_vec_unrotate(&result_point, &wp->big_attack_point, &objp->orient);
|
|
vm_vec_add(attack_point, &result_point, &objp->pos);
|
|
|
|
return;
|
|
}
|
|
wp->pick_big_attack_point_timestamp = timestamp(2000 + (int) (frand()*500.0f));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// checks valid line to target
|
|
vec3d surface_normal;
|
|
ai_bpap(objp, &attacker_objp->pos, &attacker_objp->orient.vec.fvec, attack_point, &local_attack_point, fov, 99999.9f, &surface_normal, NULL);
|
|
|
|
switch (attacker_objp->type) {
|
|
case OBJ_SHIP: {
|
|
ai_info *attacker_aip;
|
|
// if we can't find a new local_attack_point don't change local_attack_point
|
|
if (vm_vec_mag_squared(&local_attack_point) < 1) {
|
|
return;
|
|
}
|
|
|
|
attacker_aip = &Ai_info[Ships[attacker_objp->instance].ai_index];
|
|
attacker_aip->big_attack_point = local_attack_point;
|
|
attacker_aip->big_attack_surface_normal = surface_normal;
|
|
break;
|
|
}
|
|
case OBJ_WEAPON: {
|
|
weapon *wp = &Weapons[attacker_objp->instance];
|
|
wp->big_attack_point = local_attack_point;
|
|
Assert( !vm_is_vec_nan(&wp->big_attack_point) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Handler for SM_EVADE submode ( called from ai_big_chase() )
|
|
void ai_big_evade_ship()
|
|
{
|
|
vec3d player_pos, enemy_pos;
|
|
float dist;
|
|
ship *shipp = &Ships[Pl_objp->instance];
|
|
ai_info *aip = &Ai_info[shipp->ai_index];
|
|
vec3d randvec, semi_enemy_pos;
|
|
|
|
ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
|
|
|
|
dist = vm_vec_dist_quick(&player_pos, &enemy_pos);
|
|
vm_vec_rand_vec_quick(&randvec);
|
|
if ((Missiontime>>14) & 1) {
|
|
vm_vec_scale_add(&semi_enemy_pos, &enemy_pos, &randvec, dist/2.0f);
|
|
aip->prev_goal_point = semi_enemy_pos;
|
|
} else {
|
|
semi_enemy_pos = aip->prev_goal_point;
|
|
}
|
|
|
|
accelerate_ship(aip, 1.0f - ((Missiontime>>8) & 0x3f)/128.0f );
|
|
turn_away_from_point(Pl_objp, &semi_enemy_pos, 0.0f);
|
|
|
|
float box_dist;
|
|
vec3d box_vec;
|
|
int is_inside;
|
|
box_dist = get_world_closest_box_point_with_delta(&box_vec, En_objp, &player_pos, &is_inside, EVADE_BOX_BASE_DISTANCE);
|
|
if (box_dist > EVADE_BOX_BASE_DISTANCE) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
} else if ((box_dist > EVADE_BOX_MIN_DISTANCE) && (Missiontime - aip->submode_start_time > i2f(5)) ) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
|
|
//TODO TEST
|
|
|
|
/* if (dist > 4*En_objp->radius) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
} else if (dist > En_objp->radius) {
|
|
if (Missiontime - aip->submode_start_time > i2f(5)) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
} */
|
|
}
|
|
|
|
// Handler for SM_AVOID submode ( called from ai_big_chase() )
|
|
void ai_big_avoid_ship()
|
|
{
|
|
ai_big_evade_ship();
|
|
}
|
|
|
|
// reset path following information
|
|
void ai_big_subsys_path_cleanup(ai_info *aip)
|
|
{
|
|
if ( aip->ai_flags[AI::AI_Flags::On_subsys_path] ) {
|
|
aip->ai_flags.remove(AI::AI_Flags::On_subsys_path);
|
|
aip->path_goal_dist = -1;
|
|
aip->path_start = -1;
|
|
aip->path_cur = -1;
|
|
aip->path_length = 0;
|
|
}
|
|
}
|
|
|
|
// Maybe Pl_objp needs to follow a path to get in line-of-sight to a subsystem
|
|
// input: do_dot_check => default value true, flag to indicate whether check should be done to ensure
|
|
// subsystem is within certain field of view. We don't want to check fov when
|
|
// strafing, since ship is weaving to avoid turret fire
|
|
bool ai_big_maybe_follow_subsys_path(bool do_dot_check)
|
|
{
|
|
ai_info *aip;
|
|
float dot = 1.0f, min_dot;
|
|
object *target_objp;
|
|
|
|
auto shipp = &Ships[Pl_objp->instance];
|
|
aip = &Ai_info[shipp->ai_index];
|
|
|
|
if ( aip->target_objnum < 0 )
|
|
return 0;
|
|
|
|
target_objp = &Objects[aip->target_objnum];
|
|
|
|
if ( (aip->targeted_subsys != NULL) && (aip->targeted_subsys->system_info->path_num >= 0) ) {
|
|
int subsys_path_num, subsys_in_sight, checked_sight;
|
|
float dist;
|
|
|
|
// Get models of both source and target
|
|
polymodel *pm = model_get( Ship_info[shipp->ship_info_index].model_num );
|
|
polymodel_instance *pmi = model_get_instance( shipp->model_instance_num );
|
|
polymodel *pm_t = model_get( Ship_info[Ships[target_objp->instance].ship_info_index].model_num );
|
|
|
|
// Necessary sanity check
|
|
Assertion(aip->targeted_subsys->system_info->path_num < pm_t->n_paths, "Invalid Path number %d for subsystem %s on ship %s (Model: %s)\n", aip->targeted_subsys->system_info->path_num, aip->targeted_subsys->system_info->name, Ship_info[Ships[target_objp->instance].ship_info_index].name, pm_t->filename );
|
|
if (aip->targeted_subsys->system_info->path_num >= pm_t->n_paths)
|
|
return 0;
|
|
|
|
// If attacking a subsystem, ensure that we have an unobstructed line of sight... if not, then move
|
|
// towards path linked to subsystem
|
|
subsys_in_sight = 0; // assume Pl_objp doesn't have line of sight to subys
|
|
|
|
// only check for subsystem sight every N milliseconds
|
|
checked_sight = 0;
|
|
if ( timestamp_elapsed(aip->path_subsystem_next_check) ) {
|
|
vec3d geye, gsubpos;
|
|
eye *ep;
|
|
|
|
aip->path_subsystem_next_check = timestamp(1500);
|
|
|
|
// get world pos of eye (stored in geye)
|
|
ep = &(pm->view_positions[shipp->current_viewpoint]);
|
|
model_instance_local_to_global_point( &geye, &ep->pnt, pm, pmi, 0, &Pl_objp->orient, &Pl_objp->pos );
|
|
|
|
// get world pos of subsystem
|
|
vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
|
|
vm_vec_add2(&gsubpos, &En_objp->pos);
|
|
|
|
checked_sight = 1;
|
|
|
|
// Calc dot between subsys normal (based on using path info), and subsys_to_eye vector. This is
|
|
// useful later when we want to decide whether we have a realistic line-of-sight to the subsystem.
|
|
vec3d subsys_normal, subsys_to_eye;
|
|
if ( do_dot_check ) {
|
|
if ( !ship_return_subsys_path_normal(&Ships[target_objp->instance], aip->targeted_subsys, &gsubpos, &subsys_normal) ) {
|
|
vm_vec_normalized_dir(&subsys_to_eye, &geye, &gsubpos);
|
|
dot = vm_vec_dot(&subsys_normal, &subsys_to_eye);
|
|
}
|
|
}
|
|
|
|
if ( ship_subsystem_in_sight(En_objp, aip->targeted_subsys, &geye, &gsubpos, true) ) {
|
|
subsys_in_sight = 1;
|
|
}
|
|
}
|
|
|
|
// check if subsystem not in sight
|
|
min_dot = (target_objp->phys_info.fspeed > 5.0f?MIN_DOT_TO_ATTACK_MOVING_SUBSYS:MIN_DOT_TO_ATTACK_SUBSYS);
|
|
if ( (checked_sight && ((!subsys_in_sight) || (dot < min_dot)) ) ) {
|
|
|
|
subsys_path_num = aip->targeted_subsys->system_info->path_num;
|
|
if ( ((aip->path_start) == -1 || (aip->mp_index != subsys_path_num)) && subsys_path_num < pm_t->n_paths ) {
|
|
// maybe create a new path
|
|
if ( subsys_path_num >= 0 ) {
|
|
Assert(aip->target_objnum >= 0);
|
|
ai_find_path(Pl_objp, aip->target_objnum, subsys_path_num, 0, 1);
|
|
if ( aip->path_start >= 0 ) {
|
|
aip->ai_flags.set(AI::AI_Flags::On_subsys_path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( checked_sight && subsys_in_sight && (dot > min_dot) ) {
|
|
// we've got a clear shot, stay here for a bit
|
|
aip->path_subsystem_next_check = timestamp_rand(5000,8000);
|
|
}
|
|
|
|
// If there is a path that we are tracking, see if ship has gotten to within
|
|
// aip->path_goal_dist units. If so, then ship can stop following the path. This
|
|
// is required since we don't want to follow the path all the way to the subsystem,
|
|
// and we want ships to stop different distances from their path destination
|
|
if ( aip->path_length > 0 ) {
|
|
int path_done=0;
|
|
int in_view=0;
|
|
|
|
Assert(aip->path_length >= 2);
|
|
dist = vm_vec_dist_quick(&Path_points[aip->path_start+aip->path_length-2].pos, &Pl_objp->pos);
|
|
|
|
// distance setting --wookieejedi
|
|
if ( aip->path_cur >= (aip->path_start+aip->path_length-1) ) {
|
|
// register that path is done and reset goal distance to default
|
|
aip->path_goal_dist = Default_subsystem_path_pt_dist;
|
|
path_done=1;
|
|
}
|
|
// if not done then set path distances based on specified options
|
|
else {
|
|
bool found_point = false;
|
|
// 1. use radii of path points if specified
|
|
if (aip->ai_profile_flags[AI::Profile_Flags::Use_subsystem_path_point_radii]) {
|
|
// need a valid path and point in model to use pof specified radius
|
|
int path_num = aip->targeted_subsys->system_info->path_num;
|
|
if ((0 <= path_num) && (path_num < pm_t->n_paths)) {
|
|
// need a valid place in the path
|
|
if ((0 <= aip->path_cur) && (aip->path_cur < aip->path_length)) {
|
|
float path_point_radius = pm_t->paths[path_num].verts[aip->path_cur].radius;
|
|
int goal_dist = fl2i(path_point_radius);
|
|
// only set if >= Minimum path distance specified in aibig.h
|
|
if (goal_dist >= Minimum_subsystem_path_pt_dist) {
|
|
aip->path_goal_dist = goal_dist;
|
|
found_point = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 2. use radii value in ai_profiles table if specified and no valid point found
|
|
if (!found_point && The_mission.ai_profile->subsystem_path_radii) {
|
|
aip->path_goal_dist = The_mission.ai_profile->subsystem_path_radii;
|
|
}
|
|
// 3. use default radii value if no valid point or specified radii
|
|
else {
|
|
aip->path_goal_dist = Default_subsystem_path_pt_dist;
|
|
}
|
|
}
|
|
|
|
min_dot = (target_objp->phys_info.fspeed > 5.0f?MIN_DOT_TO_ATTACK_MOVING_SUBSYS:MIN_DOT_TO_ATTACK_SUBSYS);
|
|
if ( (checked_sight && subsys_in_sight) && (dot > min_dot) ) {
|
|
in_view=1;
|
|
}
|
|
|
|
// if we've reached the destination, then we can stop following the path
|
|
if ( path_done || ( dist < aip->path_goal_dist ) || in_view ) {
|
|
ai_big_subsys_path_cleanup(aip);
|
|
} else if ( dist > aip->path_goal_dist ) {
|
|
// If we have a path to follow, follow it and return
|
|
if ( aip->path_start != -1 ) {
|
|
// for now, only follow the path to the first point
|
|
if ( aip->path_cur < (aip->path_start+aip->path_length-1) ) {
|
|
if ( aip->goal_objnum != aip->target_objnum ) {
|
|
aip->previous_mode = aip->mode;
|
|
aip->mode = AIM_NONE;
|
|
aip->submode = -1;
|
|
aip->submode_start_time = Missiontime;
|
|
return 1;
|
|
}
|
|
ai_path();
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ai_new_maybe_reposition_attack_subsys() {
|
|
|
|
ai_info* aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
if (aip->target_objnum < 0 || aip->targeted_subsys == nullptr)
|
|
return false;
|
|
|
|
object* target_objp = &Objects[aip->target_objnum];
|
|
|
|
// dont bother with this if you're still far away
|
|
if (vm_vec_dist(&Pl_objp->pos, &target_objp->pos) > target_objp->radius * 2.0f + 2000.0f)
|
|
return false;
|
|
|
|
vec3d geye, gsubpos;
|
|
eye* ep;
|
|
polymodel* pm = model_get(Ship_info[Ships[Pl_objp->instance].ship_info_index].model_num);
|
|
|
|
// get world pos of eye (stored in geye)
|
|
ep = &(pm->view_positions[Ships[Pl_objp->instance].current_viewpoint]);
|
|
model_local_to_global_point(&geye, &ep->pnt, pm, 0, &Pl_objp->orient, &Pl_objp->pos);
|
|
|
|
// get world pos of subsystem
|
|
get_subsystem_pos(&gsubpos, target_objp, aip->targeted_subsys);
|
|
|
|
// you're in sight! shoot it!
|
|
if (ship_subsystem_in_sight(En_objp, aip->targeted_subsys, &geye, &gsubpos, false))
|
|
return false;
|
|
|
|
// not in sight, gotta get there
|
|
vec3d* good_pos = nullptr;
|
|
vec3d world_goal_pos;
|
|
// For performance reasons we only recheck once a second, otherwise we reuse the last found target.
|
|
if (timestamp_elapsed(aip->next_dynamic_path_check_time)) {
|
|
vec3d tgt2pl = Pl_objp->pos - target_objp->pos;
|
|
vec3d tgt2subsys = gsubpos - target_objp->pos;
|
|
float pl_dist = vm_vec_mag(&tgt2pl);
|
|
float subsys_dist = vm_vec_mag(&tgt2subsys) * 1.3f; // add a bit to this, we don't want to go directly at the subsys, we want to go above it
|
|
|
|
float angle2subsys = vm_vec_delta_ang(&tgt2pl, &tgt2subsys, nullptr);
|
|
float angle2goal = angle2subsys;
|
|
if (angle2goal > 1.2f) // let's not try to go more than about 70 degrees around the ship
|
|
angle2goal = 1.2f;
|
|
|
|
// we'll start by travelling along the sphere at our current distance around the ship
|
|
// remember that goal_pos will be relative to our target_ship (so we can scale in and out easily)
|
|
vec3d goal_pos, cross;
|
|
vm_vec_cross(&cross, &tgt2pl, &tgt2subsys);
|
|
vm_vec_normalize(&cross);
|
|
vm_vec_negate(&cross);
|
|
vm_rot_point_around_line(&goal_pos, &tgt2pl, angle2goal, &vmd_zero_vector, &cross);
|
|
|
|
// then scale in (or out) towards our subsys
|
|
float new_radius = ((subsys_dist - pl_dist) * (angle2goal / angle2subsys)) + pl_dist;
|
|
vm_vec_copy_scale(&world_goal_pos, &goal_pos, new_radius / pl_dist);
|
|
world_goal_pos += target_objp->pos;
|
|
|
|
if (pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f)) {
|
|
// ok best case won't work
|
|
if (pl_dist > subsys_dist) {
|
|
//we're farther away than the subsys, let's try at our current distance
|
|
world_goal_pos = goal_pos + target_objp->pos;
|
|
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
|
|
good_pos = &world_goal_pos;
|
|
}
|
|
|
|
// ok, let's start moving it away and see if that's better
|
|
for (float i = 1.0f; i < 4.0f; i += 1.0f) {
|
|
if (good_pos)
|
|
break;
|
|
|
|
vm_vec_copy_scale(&world_goal_pos, &goal_pos, (new_radius / pl_dist) * powf(1.3f,i));
|
|
world_goal_pos += target_objp->pos;
|
|
if (!pp_collide(&Pl_objp->pos, &world_goal_pos, target_objp, Pl_objp->radius * 4.0f))
|
|
good_pos = &world_goal_pos;
|
|
}
|
|
|
|
// welp this sucks, just run away i guess
|
|
if (!good_pos) {
|
|
vm_vec_copy_normalize(&world_goal_pos, &tgt2pl);
|
|
world_goal_pos *= target_objp->radius + pl_dist;
|
|
world_goal_pos += target_objp->pos;
|
|
good_pos = &world_goal_pos;
|
|
}
|
|
}
|
|
else
|
|
good_pos = &world_goal_pos;
|
|
aip->next_dynamic_path_check_time = timestamp( AI_DYNAMIC_PATH_RECHECK_DELAY );
|
|
aip->last_dynamic_path_goal = *good_pos;
|
|
|
|
}
|
|
else{ //Not enough time has passed till recheck.
|
|
good_pos = &aip->last_dynamic_path_goal;
|
|
}
|
|
|
|
ai_turn_towards_vector(good_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
|
|
float subsys_distance = vm_vec_dist(&Pl_objp->pos, &gsubpos);
|
|
|
|
// full speed until you're close, then slow down a bit
|
|
accelerate_ship(aip, subsys_distance > 200.0f ? 1.0f : subsys_distance / 200.0f);
|
|
|
|
if (!(Ships[Pl_objp->instance].flags[Ship::Ship_Flags::Afterburner_locked])) {
|
|
if (ai_willing_to_afterburn_hard(aip) && subsys_distance > 1000.0f)
|
|
ai_afterburn_hard(Pl_objp, aip);
|
|
else if (subsys_distance > 300.0f) {
|
|
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 2 * F1_0;
|
|
}
|
|
}
|
|
} else
|
|
afterburners_stop(Pl_objp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// This function is only called from ai_big_chase_attack() when a ship is flying very slowly and
|
|
// attacking a big ship. The ship should scan for enemy fighter/bombers... if any are close, then
|
|
// return 1, otherwise return 0;
|
|
//
|
|
// input: aip => ai_info pointer for Pl_objp
|
|
// sip => ship_info pointer for Pl_objp
|
|
//
|
|
// exit: 1 => ship should enter strafe mode
|
|
// 0 => ship should not change ai mode, no fighter/bomber threats are near
|
|
//
|
|
// NOTE: uses SCAN_FIGHTERS_INTERVAL and ENTER_STRAFE_THREAT_DIST_SQUARED which are defined in AiBig.h
|
|
int ai_big_maybe_start_strafe(ai_info *aip, ship_info *sip)
|
|
{
|
|
// if moving slowly (or stopped), and SIF_SMALL_SHIP, then enter STRAFE mode if enemy fighter/bombers
|
|
// are near
|
|
if ( sip->is_small_ship() ) {
|
|
if ( timestamp_elapsed(aip->scan_for_enemy_timestamp) ) {
|
|
object *test_objp;
|
|
ship *test_sp;
|
|
float dist_squared;
|
|
|
|
aip->scan_for_enemy_timestamp = timestamp(SCAN_FIGHTERS_INTERVAL);
|
|
// iterate through ships, and see if any fighter/bomber from opposite team are near
|
|
for (auto so: list_range(&Ship_obj_list)) {
|
|
test_objp = &Objects[so->objnum];
|
|
if (test_objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
test_sp = &Ships[test_objp->instance];
|
|
|
|
if ( iff_x_attacks_y(Ships[Pl_objp->instance].team, test_sp->team) ) {
|
|
if ( Ship_info[test_sp->ship_info_index].is_small_ship() ) {
|
|
dist_squared = vm_vec_dist_squared(&Pl_objp->pos, &test_objp->pos);
|
|
if ( dist_squared < ENTER_STRAFE_THREAT_DIST_SQUARED ) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If we've reached here, there are no enemy fighter/bombers near
|
|
return 0;
|
|
}
|
|
|
|
// ATTACK submode handler for chase mode.
|
|
void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float dist_to_enemy, int modelnum)
|
|
{
|
|
int start_bank;
|
|
float dot_to_enemy, time_to_hit;
|
|
polymodel *po = model_get( modelnum );
|
|
|
|
// Maybe evade an incoming weapon.
|
|
if (((time_to_hit = ai_endangered_by_weapon(aip)) < 4.0f) && (time_to_hit >= 0.0f)) {
|
|
aip->submode = SM_EVADE_WEAPON;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->prev_goal_point = En_objp->pos;
|
|
} else {
|
|
// If moving slowly, maybe evade incoming fire.
|
|
if (Pl_objp->phys_info.speed < 3.0f) {
|
|
object *objp;
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
if ((objp->type == OBJ_WEAPON) && (iff_x_attacks_y(Ships[Pl_objp->instance].team, Weapons[objp->instance].team)))
|
|
if (Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_LASER) {
|
|
vec3d in_vec;
|
|
float dist;
|
|
|
|
vm_vec_sub(&in_vec, &objp->pos, &Pl_objp->pos);
|
|
if (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.0f) {
|
|
dist = vm_vec_normalize(&in_vec);
|
|
if ((dist < 200.0f) && (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.95f)) {
|
|
if ((Objects[objp->parent].signature == objp->parent_sig) && (vm_vec_dist_quick(&objp->pos, &Objects[objp->parent].pos) < 300.0f)) {
|
|
set_target_objnum(aip, objp->parent);
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
} else {
|
|
aip->submode = SM_EVADE;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->prev_goal_point = En_objp->pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // end if ( Pl_objp->phys_info.speed < 3.0f )
|
|
|
|
// if moving slowly and attacking a large ship,
|
|
// check if need to enter standard strafe mode
|
|
// including ai_profile flag and if enemy fighters are near
|
|
if (Pl_objp->phys_info.speed < The_mission.ai_profile->standard_strafe_when_below_speed &&
|
|
(The_mission.ai_profile->flags[AI::Profile_Flags::Standard_strafe_used_more] || ai_big_maybe_start_strafe(aip, sip))) {
|
|
aip->previous_mode = aip->mode;
|
|
aip->mode = AIM_STRAFE;
|
|
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
|
|
ai_big_strafe_position();
|
|
return;
|
|
}
|
|
|
|
//Maybe enter glide strafe (check every 8 seconds, on a different schedule for each ship)
|
|
if ((sip->can_glide == true) && !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && static_randf((Missiontime + static_rand(aip->shipnum)) >> 19) < aip->ai_glide_strafe_percent) {
|
|
aip->previous_mode = aip->mode;
|
|
aip->mode = AIM_STRAFE;
|
|
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
|
|
aip->submode = AIS_STRAFE_GLIDE_ATTACK;
|
|
aip->submode_parm1 = 0;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
|
|
// see if Pl_objp needs to reposition to get a good shot at subsystem which is being attacked
|
|
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
|
|
if (ai_new_maybe_reposition_attack_subsys())
|
|
return;
|
|
} else if ( ai_big_maybe_follow_subsys_path() ) {
|
|
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && !ai_willing_to_afterburn_hard(aip)) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
return;
|
|
}
|
|
|
|
vec3d *rel_pos;
|
|
vec3d vec_to_enemy;
|
|
float weapon_travel_dist;
|
|
|
|
start_bank = Ships[aip->shipnum].weapons.current_primary_bank;
|
|
|
|
if ((po->n_guns) && (start_bank != -1)) {
|
|
rel_pos = &po->gun_banks[start_bank].pnt[0];
|
|
} else
|
|
rel_pos = NULL;
|
|
|
|
dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, enemy_pos, &Pl_objp->pos);
|
|
dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
|
|
|
|
vec3d rvec_vec, *rvec = &rvec_vec;
|
|
|
|
if (dist_to_enemy > 500.0f)
|
|
compute_desired_rvec(rvec, enemy_pos, &Pl_objp->pos);
|
|
else
|
|
rvec = NULL;
|
|
|
|
ai_turn_towards_vector(enemy_pos, Pl_objp, nullptr, rel_pos, 0.0f, 0, rvec);
|
|
|
|
// set an optimal range if applicable
|
|
float optimal_range = 0.0f;
|
|
ship_weapon* weapons = &Ships[Pl_objp->instance].weapons;
|
|
weapon_info* wip = nullptr;
|
|
ship* shipp = &Ships[Pl_objp->instance];
|
|
|
|
if (weapons->num_primary_banks >= 1 && weapons->current_primary_bank >= 0 && !shipp->flags[Ship::Ship_Flags::Primaries_locked]) {
|
|
wip = &Weapon_info[weapons->primary_bank_weapons[weapons->current_primary_bank]];
|
|
} else if (weapons->num_secondary_banks >= 1 && weapons->current_secondary_bank >= 0 && !shipp->flags[Ship::Ship_Flags::Secondaries_locked]) {
|
|
wip = &Weapon_info[weapons->secondary_bank_weapons[weapons->current_secondary_bank]];
|
|
}
|
|
|
|
if (wip != nullptr && wip->optimum_range > 0)
|
|
optimal_range = wip->optimum_range;
|
|
|
|
// calc range of primary weapon
|
|
weapon_travel_dist = ai_get_weapon_dist(&Ships[Pl_objp->instance].weapons);
|
|
|
|
if ( aip->targeted_subsys != NULL ) {
|
|
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist-20))) {
|
|
accelerate_ship(aip, 1.0f);
|
|
|
|
if ((aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (dot_to_enemy > 0.75f)) {
|
|
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 3*F1_0;
|
|
}
|
|
} else if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
} else {
|
|
// AL 12-31-97: Move at least as quickly as your target is moving...
|
|
accelerate_ship(aip, MAX(1.0f - dot_to_enemy, Objects[aip->target_objnum].phys_info.fspeed/sip->max_speed));
|
|
|
|
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
float accel;
|
|
if (dot_to_enemy < 0.0f) {
|
|
accel = 0.5f;
|
|
} else if (dot_to_enemy < 0.75f) {
|
|
accel = 0.75f;
|
|
} else {
|
|
if (dist_to_enemy > (optimal_range > 0.0f ? optimal_range : (weapon_travel_dist/2.0f))) {
|
|
accel = 1.0f;
|
|
} else {
|
|
accel = 1.0f - dot_to_enemy;
|
|
}
|
|
}
|
|
|
|
// use dist normal to enemy here (don't break 50 barrier)
|
|
if (dist_to_enemy < ATTACK_STOP_DISTANCE) {
|
|
// accelerate_ship(aip, accel * 0.5f);
|
|
accelerate_ship(aip, -1.0f);
|
|
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
} else {
|
|
accelerate_ship(aip, accel);
|
|
|
|
if (ai_willing_to_afterburn_hard(aip) && accel > 0.99f && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked])) {
|
|
ai_afterburn_hard(Pl_objp, aip);
|
|
} else if ((aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (accel > 0.95f)) {
|
|
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 3*F1_0;
|
|
}
|
|
} else if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handler for submode SM_CONTINUOUS_TURN
|
|
void ai_big_chase_ct()
|
|
{
|
|
ai_chase_ct();
|
|
}
|
|
|
|
extern bool ai_select_secondary_weapon(object *objp, ship_weapon *swp, int priority1 = -1, int priority2 = -1);
|
|
extern float set_secondary_fire_delay(ai_info *aip, ship *shipp, weapon_info *swip, bool burst);
|
|
extern bool ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp);
|
|
extern bool maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f);
|
|
|
|
extern void maybe_cheat_fire_synaptic(object *objp);
|
|
|
|
// Determine if Pl_objp should fire weapons at current target, based on input parameters
|
|
//
|
|
// dist_to_enemy => distance (in m) to attack point on current target
|
|
// dot_to_enemy => dot product between fvec of Pl_objp and vector from Pl_objp to attack point
|
|
//
|
|
static void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy)
|
|
{
|
|
ai_info *aip;
|
|
ship_weapon *swp;
|
|
int has_fired = -1;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
swp = &Ships[Pl_objp->instance].weapons;
|
|
|
|
float target_radius = En_objp->radius;
|
|
|
|
// normal subsystem attacking uses whether or not its on a path and skips this function entirely to stop "over-eager" firing
|
|
// since the improved method doesnt use paths, refine the target radius to stop over-eager shooting
|
|
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing] && aip->targeted_subsys != nullptr)
|
|
target_radius = aip->targeted_subsys->system_info->radius;
|
|
|
|
if (dot_to_enemy > 0.95f - 0.5f * target_radius/MAX(1.0f, target_radius + dist_to_enemy)) {
|
|
aip->time_enemy_in_range += flFrametime;
|
|
|
|
// Chance of hitting ship is based on dot product of firing ship's forward vector with vector to ship
|
|
// and also the size of the target relative to distance to target.
|
|
if (dot_to_enemy > std::max(0.5f, 0.90f + aip->ai_accuracy / 10.0f - En_objp->radius / std::max(1.0f, dist_to_enemy))) {
|
|
|
|
ship *temp_shipp;
|
|
temp_shipp = &Ships[Pl_objp->instance];
|
|
ship_weapon *tswp = &temp_shipp->weapons;
|
|
|
|
if ( tswp->num_primary_banks > 0 ) {
|
|
Assertion(tswp->current_primary_bank < tswp->num_primary_banks, "AI tried to select primary bank %d. Might be a model error\n", tswp->current_primary_bank);
|
|
weapon_info *wip = &Weapon_info[tswp->primary_bank_weapons[tswp->current_primary_bank]];
|
|
|
|
if (dist_to_enemy < MIN((wip->max_speed * wip->lifetime), wip->weapon_range)){
|
|
has_fired = 1;
|
|
if(! ai_fire_primary_weapon(Pl_objp)){
|
|
has_fired = -1;
|
|
// ship_stop_fire_primary(Pl_objp);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tswp->num_secondary_banks > 0) {
|
|
if ( !(En_objp->flags[Object::Object_Flags::Protected]) || ai_goal_is_disable_or_disarm(aip->goals[0].ai_mode) ) {
|
|
bool valid_secondary = ai_choose_secondary_weapon(Pl_objp, aip, En_objp);
|
|
int current_bank = tswp->current_secondary_bank;
|
|
if (current_bank > -1 && valid_secondary) {
|
|
weapon_info *swip = &Weapon_info[tswp->secondary_bank_weapons[current_bank]];
|
|
|
|
if ( !(En_objp->flags[Object::Object_Flags::Protected]) || (ai_goal_is_disable_or_disarm(aip->goals[0].ai_mode) && swip->wi_flags[Weapon::Info_Flags::Puncture]) ) { //override lockdown on protected ships when using anti subsystem weapons - Valathil
|
|
// If ship is protected and very low on hits, don't fire missiles.
|
|
if (!(En_objp->flags[Object::Object_Flags::Protected]) || (En_objp->hull_strength > 10*swip->damage)) {
|
|
if (aip->ai_flags[AI::AI_Flags::Unload_secondaries]) {
|
|
if (timestamp_until(swp->next_secondary_fire_stamp[current_bank]) > swip->fire_wait*1000.0f) {
|
|
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (swip->fire_wait*1000.0f));
|
|
}
|
|
}
|
|
|
|
if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank]) && weapon_target_satisfies_lock_restrictions(swip, En_objp)) {
|
|
float firing_range;
|
|
if (swip->wi_flags[Weapon::Info_Flags::Local_ssm])
|
|
firing_range=swip->lssm_lock_range;
|
|
else
|
|
firing_range = MIN((swip->max_speed * swip->lifetime), swip->weapon_range);
|
|
// reduce firing range of secondaries in nebula
|
|
extern int Nebula_sec_range;
|
|
if ((The_mission.flags[Mission::Mission_Flags::Fullneb]) && Nebula_sec_range) {
|
|
firing_range *= 0.8f;
|
|
}
|
|
|
|
float t = 0.25f; // default delay in seconds until next fire.
|
|
|
|
if (dist_to_enemy < firing_range*1.0f) {
|
|
|
|
|
|
//vm_vec_scale_add(&future_enemy_pos, enemy_pos, enemy_vel, dist_to_enemy/swip->max_speed);
|
|
//if (vm_vec_dist_quick(&future_enemy_pos, firing_pos) < firing_range * 0.8f) {
|
|
if (ai_fire_secondary_weapon(Pl_objp)) {
|
|
|
|
int current_bank_adjusted = MAX_SHIP_PRIMARY_BANKS + current_bank;
|
|
|
|
if ((aip->ai_flags[AI::AI_Flags::Unload_secondaries]) || (swip->burst_flags[Weapon::Burst_Flags::Fast_firing])) {
|
|
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
|
|
t = swip->burst_delay;
|
|
swp->burst_counter[current_bank_adjusted]++;
|
|
} else {
|
|
t = swip->fire_wait;
|
|
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
|
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
|
|
} else {
|
|
swp->burst_counter[current_bank_adjusted] = 0;
|
|
}
|
|
swp->burst_seed[current_bank_adjusted] = Random::next();
|
|
}
|
|
} else {
|
|
if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
|
|
t = set_secondary_fire_delay(aip, temp_shipp, swip, true);
|
|
swp->burst_counter[current_bank_adjusted]++;
|
|
} else {
|
|
t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
|
|
if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
|
|
swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
|
|
} else {
|
|
swp->burst_counter[current_bank_adjusted] = 0;
|
|
}
|
|
swp->burst_seed[current_bank_adjusted] = Random::next();
|
|
}
|
|
}
|
|
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
|
|
}
|
|
//}
|
|
}
|
|
swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (flFrametime < 1.0f)
|
|
aip->time_enemy_in_range *= (1.0f - flFrametime);
|
|
else
|
|
aip->time_enemy_in_range = 0;
|
|
}
|
|
|
|
if(has_fired == -1){ //stuff that hapens when the ship stops fireing
|
|
ship_stop_fire_primary(Pl_objp);
|
|
}
|
|
|
|
}
|
|
|
|
// switch ai ship into chase mode
|
|
void ai_big_switch_to_chase_mode(ai_info *aip)
|
|
{
|
|
aip->previous_mode = aip->mode;
|
|
aip->mode = AIM_CHASE;
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
|
|
extern weapon_info* ai_get_weapon(ship_weapon* swp);
|
|
|
|
// Make object Pl_objp chase object En_objp, which is a big ship, not a small ship.
|
|
void ai_big_chase()
|
|
{
|
|
float dist_to_enemy, dot_to_enemy;
|
|
vec3d player_pos, enemy_pos, vec_to_enemy;
|
|
ship_info *sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
|
|
ship *shipp = &Ships[Pl_objp->instance];
|
|
ai_info *aip = &Ai_info[shipp->ai_index];
|
|
vec3d predicted_enemy_pos;
|
|
|
|
Assert(aip->mode == AIM_CHASE);
|
|
|
|
maybe_cheat_fire_synaptic(Pl_objp);
|
|
|
|
ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
|
|
|
|
player_pos = Pl_objp->pos;
|
|
ai_big_pick_attack_point(En_objp, Pl_objp, &enemy_pos, 0.8f);
|
|
|
|
weapon_info* wip = ai_get_weapon(&shipp->weapons);
|
|
bool ballistic_aiming = !IS_VEC_NULL(&The_mission.gravity) && wip && wip->gravity_const != 0.0f;
|
|
bool valid_ballistic_trajectory = true;
|
|
|
|
if (!ballistic_aiming) {
|
|
// Compute the predicted position of the center of the ship, then add the delta to the goal pos.
|
|
if (En_objp->phys_info.speed > 3.0f) {
|
|
set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &En_objp->pos, &En_objp->phys_info.vel, aip);
|
|
vm_vec_add2(&enemy_pos, &predicted_enemy_pos);
|
|
vm_vec_sub2(&enemy_pos, &En_objp->pos);
|
|
} else
|
|
predicted_enemy_pos = En_objp->pos;
|
|
|
|
if (aip->targeted_subsys != NULL) {
|
|
get_subsystem_pos(&enemy_pos, En_objp, aip->targeted_subsys);
|
|
}
|
|
} else {
|
|
if (aip->targeted_subsys != NULL) {
|
|
get_subsystem_pos(&enemy_pos, En_objp, aip->targeted_subsys);
|
|
}
|
|
|
|
vec3d gravity = The_mission.gravity * wip->gravity_const;
|
|
vec3d target;
|
|
valid_ballistic_trajectory =
|
|
physics_lead_ballistic_trajectory(&player_pos, &enemy_pos, &En_objp->phys_info.vel, wip->max_speed, &gravity, &target);
|
|
|
|
if (valid_ballistic_trajectory) {
|
|
enemy_pos = Pl_objp->pos + target * vm_vec_dist(&Pl_objp->pos, &enemy_pos);
|
|
predicted_enemy_pos = enemy_pos;
|
|
} else {
|
|
set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &En_objp->pos, &En_objp->phys_info.vel, aip);
|
|
}
|
|
}
|
|
|
|
dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, &enemy_pos, &player_pos); // - En_objp->radius;
|
|
dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
|
|
|
|
if (aip->ai_flags[AI::AI_Flags::Target_collision]) {
|
|
if ( ai_big_strafe_maybe_retreat(&enemy_pos) ) {
|
|
aip->mode = AIM_STRAFE;
|
|
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
|
|
aip->submode = AIS_STRAFE_AVOID;
|
|
aip->submode_start_time = Missiontime;
|
|
if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (aip->ai_flags[AI::AI_Flags::Kamikaze]) {
|
|
//nprintf(("AI", "Kamikaze: %7.3f %7.3f\n", dot_to_enemy, dist_to_enemy));
|
|
accelerate_ship(aip, 1.0f);
|
|
if ((dist_to_enemy < 400.0f) && ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 3*F1_0;
|
|
}
|
|
}
|
|
|
|
// If just acquired target, or target is not in reasonable cone, don't refine believed enemy position.
|
|
if ((dot_to_enemy < 0.25f) || (aip->target_time < 1.0f) || (aip->ai_flags[AI::AI_Flags::Seek_lock])) {
|
|
update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
|
|
} else if (aip->targeted_subsys != NULL && !ballistic_aiming) {
|
|
Assert(aip->targeted_subsys != NULL);
|
|
get_subsystem_pos(&enemy_pos, En_objp, aip->targeted_subsys);
|
|
vm_vec_add2(&enemy_pos, &predicted_enemy_pos);
|
|
vm_vec_sub2(&enemy_pos, &En_objp->pos);
|
|
dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, &enemy_pos, &player_pos); // - En_objp->radius;
|
|
dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
|
|
update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy, En_objp->radius);
|
|
} else if (En_objp->flags[Object::Object_Flags::Protected]) { // If protected and we're not attacking a subsystem, stop attacking!
|
|
update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
|
|
aip->target_objnum = -1;
|
|
if (find_enemy(OBJ_INDEX(Pl_objp), MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level]) == -1) {
|
|
ai_do_default_behavior(Pl_objp);
|
|
return;
|
|
}
|
|
} else {
|
|
update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
|
|
}
|
|
|
|
// If recently acquired target and not looking at target, just turn a bit.
|
|
switch (aip->submode) {
|
|
case SM_ATTACK:
|
|
case SM_SUPER_ATTACK:
|
|
if (vm_vec_dist_quick(&Pl_objp->pos, &predicted_enemy_pos) > 100.0f + En_objp->radius * 2.0f) {
|
|
if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &predicted_enemy_pos, 10.0f)) {
|
|
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!aip->ai_profile_flags[AI::Profile_Flags::No_continuous_turn_on_attack] && aip->target_time < 2.0f)
|
|
if ((dot_to_enemy < 0.9f) || (dist_to_enemy > 300.0f)) {
|
|
aip->submode = SM_CONTINUOUS_TURN;
|
|
aip->submode_start_time = Missiontime - fl2f(2.75f); // This backdated start time allows immediate switchout.
|
|
if (dot_to_enemy > 0.0f)
|
|
aip->target_time += flFrametime * dot_to_enemy;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Set turn and acceleration based on submode.
|
|
//
|
|
|
|
if (((aip->submode != SM_ATTACK) && (aip->submode != SM_SUPER_ATTACK) && (aip->submode != SM_ATTACK_FOREVER) && (aip->submode != SM_EVADE_WEAPON)) || (aip->mode == AIM_STRAFE)){
|
|
if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
}
|
|
|
|
switch (aip->submode) {
|
|
case SM_CONTINUOUS_TURN:
|
|
ai_big_chase_ct();
|
|
break;
|
|
|
|
case SM_ATTACK:
|
|
case SM_SUPER_ATTACK:
|
|
case SM_ATTACK_FOREVER:
|
|
ai_big_chase_attack(aip, sip, &enemy_pos, dist_to_enemy, sip->model_num);
|
|
break;
|
|
|
|
case SM_EVADE:
|
|
ai_big_evade_ship();
|
|
break;
|
|
|
|
case SM_AVOID:
|
|
ai_big_avoid_ship();
|
|
break;
|
|
|
|
case SM_EVADE_WEAPON:
|
|
evade_weapon();
|
|
break;
|
|
|
|
default:
|
|
aip->last_attack_time = Missiontime;
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
break;
|
|
}
|
|
|
|
// maybe Pl_objp was forced into strafe mode, if so return now
|
|
if ( aip->mode == AIM_STRAFE )
|
|
return;
|
|
|
|
// Maybe choose a new submode.
|
|
if (aip->submode != SM_AVOID && aip->submode != SM_EVADE ) {
|
|
// If a very long time since attacked, attack no matter what!
|
|
if (aip->submode != SM_SUPER_ATTACK) {
|
|
if (Missiontime - aip->last_attack_time > i2f(6)) {
|
|
aip->submode = SM_SUPER_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->last_attack_time = Missiontime;
|
|
}
|
|
}
|
|
|
|
// If a collision is expected, pull out!
|
|
if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Kamikaze_no_collision_avoidance] && aip->ai_flags[AI::AI_Flags::Kamikaze])) {
|
|
|
|
float dist_normal_to_enemy;
|
|
|
|
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
|
|
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing])
|
|
dist_normal_to_enemy = fl_abs((dist_to_enemy / vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
|
else // I'm fairly sure this retail code VVV meant to use / instead of *
|
|
dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
|
|
}
|
|
else {
|
|
// don;t have normal so use a conservative value here
|
|
dist_normal_to_enemy = 0.3f * dist_to_enemy;
|
|
}
|
|
|
|
// float time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed * fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
|
|
// if (Framecount % 30 == 1) {
|
|
// mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
|
|
// }
|
|
|
|
// since we're not in strafe and we may get a bad normal, cap dist_normal_to_enemy as MIN(0.3*dist_to_enemy, self)
|
|
// this will allow us to get closer on a bad normal
|
|
dist_normal_to_enemy = MAX(0.3f * dist_to_enemy, dist_normal_to_enemy);
|
|
|
|
if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
|
|
// within 50m or 1sec
|
|
float time_to_enemy;
|
|
if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
|
|
if (Pl_objp->phys_info.speed > 0.1) {
|
|
time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
|
|
}
|
|
else {
|
|
// a big time
|
|
time_to_enemy = 100.0f;
|
|
}
|
|
}
|
|
else {
|
|
if (Pl_objp->phys_info.speed > 0.1) {
|
|
time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
|
|
}
|
|
else {
|
|
// a big time
|
|
time_to_enemy = 100.0f;
|
|
}
|
|
}
|
|
|
|
float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed - 50) * 2);
|
|
if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME)) {
|
|
// get away, simulate crsh recovery (don't use avoid)
|
|
// accelerate_ship(aip, -1.0f);
|
|
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
|
|
// aip->submode = SM_AVOID;
|
|
// aip->submode_start_time = Missiontime;
|
|
}
|
|
else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME)) {
|
|
// slow down
|
|
accelerate_ship(aip, -1.0f);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f)) {
|
|
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
|
|
if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
|
|
accelerate_ship(aip, -1.0f);
|
|
} else {
|
|
aip->submode = SM_AVOID;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
}
|
|
} */
|
|
}
|
|
}
|
|
|
|
switch (aip->submode) {
|
|
case SM_CONTINUOUS_TURN:
|
|
if (Missiontime - aip->submode_start_time > i2f(3)) {
|
|
aip->last_attack_time = Missiontime;
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
break;
|
|
|
|
case SM_ATTACK:
|
|
case SM_ATTACK_FOREVER:
|
|
break;
|
|
|
|
case SM_EVADE:
|
|
if (dist_to_enemy > 4*En_objp->radius) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->last_attack_time = Missiontime;
|
|
}
|
|
break;
|
|
|
|
case SM_SUPER_ATTACK:
|
|
break;
|
|
|
|
case SM_AVOID:
|
|
// if outside box by > 300 m
|
|
// DA 4/20/98 can never get here attacking a big ship
|
|
{
|
|
int is_inside;
|
|
float box_dist = get_world_closest_box_point_with_delta(NULL, En_objp, &Pl_objp->pos, &is_inside, 0.0f);
|
|
|
|
if (box_dist > 300) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->last_attack_time = Missiontime;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* if (dot_to_enemy > -0.2f) {
|
|
aip->submode_start_time = Missiontime;
|
|
} else if (Missiontime - aip->submode_start_time > i2f(1)/2) {
|
|
if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, 3.0f)) {
|
|
aip->submode_start_time = Missiontime;
|
|
} else {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->last_attack_time = Missiontime;
|
|
}
|
|
}
|
|
|
|
break;*/
|
|
|
|
case SM_EVADE_WEAPON:
|
|
if (aip->danger_weapon_objnum == -1) {
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->last_attack_time = Missiontime;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
aip->submode = SM_ATTACK;
|
|
aip->submode_start_time = Missiontime;
|
|
aip->last_attack_time = Missiontime;
|
|
}
|
|
|
|
//
|
|
// Maybe fire primary weapon and update time_enemy_in_range
|
|
//
|
|
//nprintf(("AI", "time_enemy_in_range = %7.3f, dot = %7.3f\n", aip->time_enemy_in_range, dot_to_enemy));
|
|
|
|
// AL: add condition that Pl_objp must not be following a path to fire. This may be too extreme, but
|
|
// I noticed AI ships firing inappropriately when following a path near a big ship.
|
|
// TODO: investigate why ships fire (and aren't close to hitting ship) when following a path near
|
|
// a big ship
|
|
if (aip->mode != AIM_EVADE && aip->path_start == -1 && (!ballistic_aiming || valid_ballistic_trajectory)) {
|
|
ai_big_maybe_fire_weapons(dist_to_enemy, dot_to_enemy);
|
|
} else {
|
|
if (flFrametime < 1.0f)
|
|
aip->time_enemy_in_range *= (1.0f - flFrametime);
|
|
else
|
|
aip->time_enemy_in_range = 0;
|
|
}
|
|
}
|
|
|
|
// all three parms are output parameters
|
|
// Enemy object is En_objp
|
|
// pos => world pos of attack point
|
|
// dist => distance from Pl_objp front to attack point
|
|
// dot => dot of Pl_objp fvec and vector to attack point
|
|
// fire_pos => world pos from which firing
|
|
void ai_big_attack_get_data(vec3d *enemy_pos, float *dist_to_enemy, float *dot_to_enemy)
|
|
{
|
|
vec3d player_pos, vec_to_enemy, predicted_enemy_pos;
|
|
ship *shipp = &Ships[Pl_objp->instance];
|
|
ai_info *aip = &Ai_info[shipp->ai_index];
|
|
ship_info *esip = &Ship_info[Ships[En_objp->instance].ship_info_index];
|
|
|
|
Assert(aip->mode == AIM_STRAFE);
|
|
|
|
// ensure that Pl_objp is still targeting a big ship
|
|
if ( !(esip->is_big_or_huge()) ) {
|
|
ai_big_switch_to_chase_mode(aip);
|
|
return;
|
|
}
|
|
|
|
ai_set_positions(Pl_objp, En_objp, aip, &player_pos, enemy_pos);
|
|
|
|
player_pos = Pl_objp->pos;
|
|
|
|
if (aip->targeted_subsys != NULL) {
|
|
Assert(aip->targeted_subsys != NULL);
|
|
get_subsystem_pos(enemy_pos, En_objp, aip->targeted_subsys);
|
|
} else {
|
|
// checks valid line to target
|
|
ai_big_pick_attack_point(En_objp, Pl_objp, enemy_pos, 0.8f);
|
|
}
|
|
|
|
// Take player pos to be center of ship + ship_radius
|
|
vm_vec_scale_add2(&player_pos, &Pl_objp->orient.vec.fvec, Pl_objp->radius);
|
|
|
|
// If seeking lock, try to point directly at ship, else predict position so lasers can hit it.
|
|
// If just acquired target, or target is not in reasonable cone, don't refine believed enemy position.
|
|
vm_vec_normalized_dir(&vec_to_enemy, enemy_pos, &player_pos);
|
|
*dot_to_enemy=vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
|
|
if ((*dot_to_enemy < 0.25f) || (aip->target_time < 1.0f) || (aip->ai_flags[AI::AI_Flags::Seek_lock])) {
|
|
predicted_enemy_pos=*enemy_pos;
|
|
} else {
|
|
vec3d gun_pos, pnt;
|
|
polymodel *po = model_get( Ship_info[shipp->ship_info_index].model_num );
|
|
float weapon_speed;
|
|
|
|
// Compute position of gun in absolute space and use that as fire position.
|
|
if (po->n_guns > 0 && !(The_mission.ai_profile->flags[AI::Profile_Flags::Ai_aims_from_ship_center])) {
|
|
pnt = po->gun_banks[0].pnt[0];
|
|
vm_vec_unrotate(&gun_pos, &pnt, &Pl_objp->orient);
|
|
vm_vec_add2(&gun_pos, &Pl_objp->pos);
|
|
} else {
|
|
//Use the convergence offset, if there is one
|
|
vm_vec_copy_scale(&pnt, &Ship_info[shipp->ship_info_index].convergence_offset, 1.0f);
|
|
}
|
|
weapon_speed = ai_get_weapon_speed(&shipp->weapons);
|
|
|
|
set_predicted_enemy_pos_turret(&predicted_enemy_pos, &gun_pos, Pl_objp, enemy_pos, &En_objp->phys_info.vel, weapon_speed, aip->time_enemy_in_range);
|
|
}
|
|
|
|
*dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, &predicted_enemy_pos, &player_pos);
|
|
*dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
|
|
update_aspect_lock_information(aip, &vec_to_enemy, *dist_to_enemy, En_objp->radius);
|
|
|
|
*enemy_pos = predicted_enemy_pos;
|
|
}
|
|
|
|
// check to see if Pl_objp has gotten too close to attacking point.. if so, break off by entering
|
|
// AIS_STRAFE_RETREAT
|
|
static bool ai_big_strafe_maybe_retreat(const vec3d *target_pos)
|
|
{
|
|
ai_info *aip;
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
vec3d vec_to_target;
|
|
vm_vec_sub(&vec_to_target, target_pos, &Pl_objp->pos);
|
|
|
|
float dist_to_target = vm_vec_mag(&vec_to_target);
|
|
|
|
// add distance penalty for going too fast
|
|
float speed_to_dist_penalty = MAX(0.0f, (Pl_objp->phys_info.speed-50));
|
|
|
|
bool collide_time;
|
|
bool collide_distance;
|
|
if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_standard_strafe]) {
|
|
// check if ship facing target, as likely will only collide if facing
|
|
vec3d vec_to_tpos;
|
|
vm_vec_normalized_dir(&vec_to_tpos, target_pos, &Pl_objp->pos);
|
|
float dot = vm_vec_dot(&vec_to_tpos, &Pl_objp->orient.vec.fvec);
|
|
if (dot <= 0.0f) {
|
|
collide_time = false;
|
|
collide_distance = false;
|
|
} else {
|
|
collide_time = (dist_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
|
|
collide_distance = dist_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
|
|
}
|
|
} else {
|
|
float dist_normal_to_target;
|
|
if (vm_vec_mag(&aip->big_attack_surface_normal) > 0.9) {
|
|
dist_normal_to_target =
|
|
MAX(0.2f * dist_to_target, -vm_vec_dot(&vec_to_target, &aip->big_attack_surface_normal));
|
|
} else {
|
|
dist_normal_to_target = 0.2f * dist_to_target;
|
|
}
|
|
collide_time = (dist_normal_to_target / Pl_objp->phys_info.speed) < STRAFE_RETREAT_COLLIDE_TIME;
|
|
collide_distance = dist_normal_to_target < (STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty);
|
|
}
|
|
|
|
//if ((dot_to_enemy > 1.0f - 0.1f * En_objp->radius/(dist_to_enemy + 1.0f)) && (Pl_objp->phys_info.speed > dist_to_enemy/5.0f))
|
|
|
|
// Inside 2 sec retreat, setting goal point to box point + strafe_retreat_box_dist
|
|
// If collision, use std collision resolution.
|
|
if ( !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && ((aip->ai_flags[AI::AI_Flags::Target_collision]) || (collide_time) || (collide_distance)) ) {
|
|
if (aip->ai_flags[AI::AI_Flags::Target_collision]) {
|
|
// use standard collision resolution
|
|
aip->ai_flags.remove(AI::AI_Flags::Target_collision);
|
|
big_ship_collide_recover_start(Pl_objp, En_objp, nullptr);
|
|
} else {
|
|
// too close for comfort so fly to box point + strafe_retreat_box_dist
|
|
aip->submode = AIS_STRAFE_RETREAT1;
|
|
aip->submode_start_time = Missiontime;
|
|
|
|
int is_inside;
|
|
vec3d goal_point;
|
|
get_world_closest_box_point_with_delta(&goal_point, En_objp, &Pl_objp->pos, &is_inside, The_mission.ai_profile->strafe_retreat_box_dist);
|
|
|
|
// set goal point
|
|
aip->goal_point = goal_point;
|
|
|
|
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 3*F1_0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// attack directly to the turret and fire weapons
|
|
void ai_big_strafe_attack()
|
|
{
|
|
ai_info *aip;
|
|
vec3d target_pos;
|
|
vec3d rand_vec;
|
|
float accel, t;
|
|
float target_dist = 0.0f;
|
|
float target_dot = 0.0f;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
|
|
if (ai_new_maybe_reposition_attack_subsys())
|
|
return;
|
|
} else if (ai_big_maybe_follow_subsys_path(false))
|
|
return;
|
|
|
|
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
|
if ( ai_big_strafe_maybe_retreat(&target_pos) )
|
|
return;
|
|
|
|
if (aip->ai_flags[AI::AI_Flags::Kamikaze]) {
|
|
if (target_dist < 1200.0f) {
|
|
ai_turn_towards_vector(&target_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
|
|
accelerate_ship(aip, 1.0f);
|
|
if ((target_dist < 400.0f) && ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 3*F1_0;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!(aip->ai_flags[AI::AI_Flags::Seek_lock]) || (aip->aspect_locked_time < 2.0f)) {
|
|
t = ai_endangered_by_weapon(aip);
|
|
if ( t > 0.0f && t < 1.5f ) {
|
|
// set up goal_point for avoid path to turn towards
|
|
aip->goal_point = Pl_objp->pos;
|
|
switch(Random::next(4)) {
|
|
case 0:
|
|
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, 2.0f);
|
|
break;
|
|
case 1:
|
|
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, -2.0f);
|
|
break;
|
|
case 2:
|
|
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec, 2.0f);
|
|
break;
|
|
case 3:
|
|
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec, -2.0f);
|
|
break;
|
|
} // end switch
|
|
|
|
vm_vec_scale(&rand_vec, 1000.0f);
|
|
vm_vec_add2(&aip->goal_point, &rand_vec);
|
|
|
|
aip->submode = AIS_STRAFE_AVOID;
|
|
aip->submode_start_time = Missiontime;
|
|
|
|
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + fl2f(0.5f);
|
|
}
|
|
}
|
|
}
|
|
|
|
// maybe fire weapons at target
|
|
ai_big_maybe_fire_weapons(target_dist, target_dot);
|
|
turn_towards_point(Pl_objp, &target_pos, NULL, 0.0f);
|
|
|
|
fix last_hit = Missiontime - aip->last_hit_time;
|
|
if ( aip->ai_profile_flags[AI::Profile_Flags::Ai_can_slow_down_attacking_big_ships] ) {
|
|
// Slow down if we've not been hit for a while
|
|
if ( target_dist > 1200 || last_hit < F1_0*6) {
|
|
accel = 1.0f;
|
|
} else {
|
|
float attack_time;
|
|
attack_time = f2fl(Missiontime - aip->submode_start_time);
|
|
if ( attack_time > 15 ) {
|
|
accel = 0.2f;
|
|
} else if ( attack_time > 10 ) {
|
|
accel = 0.4f;
|
|
} else if ( attack_time > 8 ) {
|
|
accel = 0.6f;
|
|
} else if ( attack_time > 5 ) {
|
|
accel = 0.8f;
|
|
} else {
|
|
accel = 1.0f;
|
|
}
|
|
}
|
|
} else {
|
|
accel = 1.0f;
|
|
}
|
|
|
|
accelerate_ship(aip, accel);
|
|
|
|
// if haven't been hit in quite a while, leave strafe mode
|
|
fix long_enough = fl2f(The_mission.ai_profile->strafe_max_unhit_time);
|
|
if ( (last_hit > long_enough) && ( (Missiontime - aip->submode_parm0) > long_enough) ) {
|
|
ai_big_switch_to_chase_mode(aip);
|
|
}
|
|
}
|
|
|
|
// SUSHI: Strafe attack using glide
|
|
//submode_parm1 encodes the stage we're in.
|
|
//0: Choosing goal point to fly towards
|
|
//1: Accelerating towards the goal point.
|
|
//2: Raining down fire and brimstone on the target.
|
|
//3: Dummy stage used when resetting or exiting the mode
|
|
void ai_big_strafe_glide_attack()
|
|
{
|
|
ai_info *aip;
|
|
vec3d target_pos, vec_to_goal, predicted_enemy_pos, norm_vel_vec;
|
|
float target_dist, target_dot, target_ship_dist, dot_to_goal, flight_dot_to_enemy;
|
|
object *target_objp;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
target_objp = &Objects[aip->target_objnum];
|
|
|
|
vm_vec_sub(&vec_to_goal, &aip->goal_point, &Pl_objp->pos);
|
|
vm_vec_normalize(&vec_to_goal);
|
|
|
|
if (vm_vec_mag(&Pl_objp->phys_info.vel) > 0.0f)
|
|
vm_vec_copy_normalize(&norm_vel_vec, &Pl_objp->phys_info.vel);
|
|
else
|
|
vm_vec_copy_normalize(&norm_vel_vec, &Pl_objp->orient.vec.fvec);
|
|
|
|
dot_to_goal = vm_vec_dot(&vec_to_goal, &norm_vel_vec); //Angle between flight path and goal point
|
|
flight_dot_to_enemy = vm_vec_dot(&target_objp->pos, &norm_vel_vec); //Angle between flight path and target ship
|
|
|
|
//If we're following a path, then we shouldn't be doing glide strafe
|
|
if (aip->ai_profile_flags[AI::Profile_Flags::Improved_subsystem_attack_pathing]) {
|
|
if (ai_new_maybe_reposition_attack_subsys())
|
|
return;
|
|
} else if (ai_big_maybe_follow_subsys_path(false))
|
|
return;
|
|
|
|
//Gets a point on the target ship to attack, as well as distance and the angle between the nose of the attacker and that point.
|
|
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
|
target_ship_dist = vm_vec_dist(&target_objp->pos, &Pl_objp->pos);
|
|
|
|
//If we haven't chosen the goal point yet, do so now
|
|
if (aip->submode_parm1 == 0) {
|
|
//Pick goal point. This should be a random point that passes through the circle whose normal is the vector between attacker and target
|
|
vec3d targetToAttackerDir;
|
|
vm_vec_normalized_dir(&targetToAttackerDir, &Pl_objp->pos, &target_objp->pos);
|
|
matrix orient;
|
|
vm_vector_2_matrix_norm(&orient, &targetToAttackerDir, nullptr, nullptr);
|
|
vec3d tangentPoint;
|
|
vm_vec_random_in_circle(&tangentPoint, &target_objp->pos, &orient, target_objp->radius + GLIDE_STRAFE_DISTANCE, true);
|
|
//Get tangent point in coords relative to ship, scale it up so the actual goal point is far away, then put back in world coords
|
|
vm_vec_sub2(&tangentPoint, &Pl_objp->pos);
|
|
vm_vec_scale(&tangentPoint, 1000.0f);
|
|
vm_vec_add(&aip->goal_point, &tangentPoint, &Pl_objp->pos);
|
|
aip->submode_parm1 = 1;
|
|
}
|
|
if (aip->submode_parm1 == 1) {
|
|
//Stay in acceleration stage?
|
|
//Accelerate towards goal point until:
|
|
// Mostly up to speed AND
|
|
// Moving in the correct direction
|
|
if ((Pl_objp->phys_info.fspeed > (Pl_objp->phys_info.max_vel.xyz.z * 0.85f)) && (dot_to_goal > 0.99f))
|
|
aip->submode_parm1 = 2;
|
|
}
|
|
if (aip->submode_parm1 == 2) {
|
|
//Should we stay in the fire stage?
|
|
//Keep going until we are too far away.
|
|
//If we are still on approach but too far away, this will still trigger. This will allow us to reposition the target
|
|
//point and allow for a "jinking" effect.
|
|
if (target_ship_dist > (The_mission.ai_profile->strafe_retreat_box_dist + target_objp->radius) &&
|
|
Missiontime - aip->submode_start_time > i2f(GLIDE_STRAFE_MIN_TIME)) {
|
|
//This checks whether we are moving toward the target or away from it. If moving towards, we reset the stage so that we
|
|
//pick a new attack vector (jinking). If moving away, we're at the end of a run so do a full reset (possibly allowing a
|
|
//switch back to conventional strafe).
|
|
if (flight_dot_to_enemy > 0.0f) {
|
|
aip->submode_parm1 = 0;
|
|
}
|
|
else {
|
|
aip->submode_parm1 = 3;
|
|
aip->submode = AIS_STRAFE_POSITION;
|
|
}
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
}
|
|
//Time limit: if we've taken too long, reset
|
|
if (Missiontime - aip->submode_start_time > i2f(GLIDE_STRAFE_MAX_TIME)) {
|
|
aip->submode_parm1 = 3;
|
|
aip->submode = AIS_STRAFE_POSITION;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
|
|
//Acceleration stage
|
|
if (aip->submode_parm1 == 1) {
|
|
accelerate_ship(aip, 1.0f);
|
|
//Use afterburners if we have them and are pointed the right way
|
|
if (dot_to_goal > 0.99f) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + 3*F1_0;
|
|
}
|
|
|
|
turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
|
|
}
|
|
//Fire stage
|
|
if (aip->submode_parm1 == 2) {
|
|
Pl_objp->phys_info.flags |= PF_GLIDING;
|
|
accelerate_ship(aip, 0.0f);
|
|
afterburners_stop(Pl_objp);
|
|
|
|
//Compensate for motion of ship and target
|
|
set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &En_objp->pos, &En_objp->phys_info.vel, aip);
|
|
vm_vec_add2(&target_pos, &predicted_enemy_pos);
|
|
vm_vec_sub2(&target_pos, &En_objp->pos);
|
|
|
|
turn_towards_point(Pl_objp, &target_pos, NULL, 0.0f);
|
|
ai_big_maybe_fire_weapons(target_dist, target_dot);
|
|
}
|
|
|
|
// if haven't been hit in quite a while, leave strafe mode
|
|
// (same as ai_big_strafe_attack)
|
|
fix long_enough = fl2f(The_mission.ai_profile->strafe_max_unhit_time);
|
|
if ( (Missiontime - aip->last_hit_time > long_enough) && ( (Missiontime - aip->submode_parm0) > long_enough) ) {
|
|
ai_big_switch_to_chase_mode(aip);
|
|
}
|
|
}
|
|
|
|
// pick a new attack point when entering this state, and keep using it
|
|
void ai_big_strafe_avoid()
|
|
{
|
|
ai_info *aip;
|
|
vec3d target_pos;
|
|
float target_dist, target_dot;
|
|
fix mode_time;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
mode_time = Missiontime - aip->submode_start_time;
|
|
|
|
ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
|
|
if ( ai_big_strafe_maybe_retreat(&target_pos) )
|
|
return;
|
|
|
|
if ( mode_time > fl2f(0.5)) {
|
|
ai_big_strafe_position();
|
|
}
|
|
|
|
turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
|
|
accelerate_ship(aip, 1.0f);
|
|
}
|
|
|
|
// move towards aip->goal_point in an evasive manner
|
|
void ai_big_strafe_retreat1()
|
|
{
|
|
float dist;
|
|
ai_info *aip;
|
|
vec3d rand_vec;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
dist = vm_vec_dist_quick(&Pl_objp->pos, &aip->goal_point);
|
|
if ( dist < 70 ) {
|
|
aip->submode = AIS_STRAFE_POSITION;
|
|
aip->submode_start_time = Missiontime;
|
|
// nprintf(("Alan","Ship %s entering AIS_STRAFE_POSITION\n", Ships[aip->shipnum].ship_name));
|
|
return;
|
|
}
|
|
|
|
if (Missiontime - aip->submode_start_time > fl2f(1.50f)) {
|
|
// set up goal_point for avoid path to turn towards
|
|
aip->prev_goal_point = Pl_objp->pos;
|
|
switch(Random::next(4)) {
|
|
case 0:
|
|
vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec);
|
|
break;
|
|
case 1:
|
|
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, -1.0f);
|
|
break;
|
|
case 2:
|
|
vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec);
|
|
break;
|
|
case 3:
|
|
vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec, -1.0f);
|
|
break;
|
|
} // end switch
|
|
|
|
//vm_vec_scale(&rand_vec, 200.0f);
|
|
//vm_vec_add2(&aip->prev_goal_point, &rand_vec);
|
|
vm_vec_scale_add(&aip->prev_goal_point, &aip->goal_point, &rand_vec, 200.0f);
|
|
|
|
aip->submode = AIS_STRAFE_RETREAT2;
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
|
|
turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
|
|
accelerate_ship(aip, 1.0f);
|
|
}
|
|
|
|
void ai_big_strafe_retreat2()
|
|
{
|
|
float dist;
|
|
ai_info *aip;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON ) {
|
|
if (Missiontime > aip->afterburner_stop_time) {
|
|
//nprintf(("AI", "Frame %i, turning off afterburner.\n", AI_FrameCount));
|
|
afterburners_stop(Pl_objp);
|
|
}
|
|
}
|
|
|
|
dist = vm_vec_dist_quick(&Pl_objp->pos, &aip->goal_point);
|
|
if ( dist < 70 ) {
|
|
aip->submode = AIS_STRAFE_POSITION;
|
|
aip->submode_start_time = Missiontime;
|
|
// nprintf(("Alan","Ship %s entering AIS_STRAFE_POSITION\n", Ships[aip->shipnum].ship_name));
|
|
return;
|
|
}
|
|
|
|
if (Missiontime - aip->submode_start_time > fl2f(0.70f)) {
|
|
aip->submode = AIS_STRAFE_RETREAT1;
|
|
aip->submode_start_time = Missiontime;
|
|
|
|
if ( (Missiontime - aip->last_hit_time) < F1_0*5 ) {
|
|
if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
|
|
afterburners_start(Pl_objp);
|
|
aip->afterburner_stop_time = Missiontime + F1_0;
|
|
}
|
|
}
|
|
}
|
|
|
|
turn_towards_point(Pl_objp, &aip->prev_goal_point, NULL, 0.0f);
|
|
accelerate_ship(aip, 1.0f);
|
|
}
|
|
|
|
// reposition self to begin another strafing run
|
|
void ai_big_strafe_position()
|
|
{
|
|
ship_info *sip;
|
|
ai_info *aip;
|
|
sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
//Maybe use AIS_STRAFE_GLIDE_ATTACK
|
|
if ((sip->can_glide == true) && !(aip->ai_flags[AI::AI_Flags::Kamikaze]) && (frand() < aip->ai_glide_strafe_percent)) {
|
|
aip->submode = AIS_STRAFE_GLIDE_ATTACK;
|
|
aip->submode_parm1 = 0;
|
|
} else {
|
|
aip->submode = AIS_STRAFE_ATTACK;
|
|
}
|
|
aip->submode_start_time = Missiontime;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------
|
|
// #define AIS_STRAFE_ATTACK 201 // fly towards target and attack
|
|
// #define AIS_STRAFE_AVOID 202 // fly evasive vector to avoid incoming fire
|
|
// #define AIS_STRAFE_RETREAT1 203 // fly away from attack point (directly)
|
|
// #define AIS_STRAFE_RETREAT1 204 // fly away from attack point (on an avoid vector)
|
|
// #define AIS_STRAFE_POSITION 205 // re-position to resume strafing attack
|
|
//
|
|
void ai_big_strafe()
|
|
{
|
|
ai_info *aip;
|
|
|
|
aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
|
|
|
|
Assert(aip->mode == AIM_STRAFE);
|
|
|
|
/*
|
|
if ( aip->goal_objnum != aip->target_objnum ) {
|
|
Assertion(false, "The goal objnum does not match the target objnum in ai_big_strafe(), please report to the SCP!"); // what is going on here? - Get Alan
|
|
aip->mode = AIM_NONE;
|
|
return;
|
|
}
|
|
*/
|
|
// check if target is still a big ship... if not enter chase mode
|
|
if ( !(Ship_info[Ships[En_objp->instance].ship_info_index].is_big_or_huge()) ) {
|
|
ai_big_switch_to_chase_mode(aip);
|
|
return;
|
|
}
|
|
|
|
switch (aip->submode) {
|
|
case AIS_STRAFE_ATTACK:
|
|
ai_big_strafe_attack();
|
|
break;
|
|
case AIS_STRAFE_AVOID:
|
|
ai_big_strafe_avoid();
|
|
break;
|
|
case AIS_STRAFE_RETREAT1:
|
|
ai_big_strafe_retreat1();
|
|
break;
|
|
case AIS_STRAFE_RETREAT2:
|
|
ai_big_strafe_retreat2();
|
|
break;
|
|
case AIS_STRAFE_POSITION:
|
|
ai_big_strafe_position();
|
|
break;
|
|
case AIS_STRAFE_GLIDE_ATTACK:
|
|
ai_big_strafe_glide_attack();
|
|
break;
|
|
default:
|
|
|
|
Error(LOCATION, "Ai_big_strafe() just tried to use an illegal submode. Go tell a coder!");
|
|
break;
|
|
}
|
|
|
|
//Maybe apply random sidethrust, depending on the current submode
|
|
//The following are valid targets for random sidethrust (circle strafe uses it too, but that is handled separately)
|
|
if (aip->submode == AIS_STRAFE_ATTACK ||
|
|
aip->submode == AIS_STRAFE_AVOID ||
|
|
aip->submode == AIS_STRAFE_RETREAT1 ||
|
|
aip->submode == AIS_STRAFE_RETREAT2 ||
|
|
aip->submode == AIS_STRAFE_POSITION)
|
|
{
|
|
//Re-roll for random sidethrust every 2 seconds
|
|
if (static_randf((Missiontime + static_rand(aip->shipnum)) >> 17) < aip->ai_random_sidethrust_percent) {
|
|
do_random_sidethrust(aip, &Ship_info[Ships[aip->shipnum].ship_info_index]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if Pl_objp should enter strafe mode (This is called from maybe_evade_dumbfire_weapon(), and the
|
|
// weapon_objnum is for a weapon that is about to collide with Pl_objp
|
|
//
|
|
// Check if weapon_objnum was fired by Pl_objp's target, and whether Pl_objp's target is a big ship, if
|
|
// so, enter AIM_STRAFE
|
|
int ai_big_maybe_enter_strafe_mode(const object *pl_objp, int weapon_objnum)
|
|
{
|
|
ai_info *aip;
|
|
ship_info *sip;
|
|
object *weapon_objp, *parent_objp;
|
|
|
|
aip = &Ai_info[Ships[pl_objp->instance].ai_index];
|
|
Assert(aip->mode != AIM_STRAFE); // can't happen
|
|
|
|
// if Pl_objp has no target, then we can't enter strafe mode
|
|
if ( aip->target_objnum < 0 ) {
|
|
return 0;
|
|
}
|
|
|
|
// if target is not a ship, stafe mode is not possible
|
|
if ( Objects[aip->target_objnum].type != OBJ_SHIP ) {
|
|
return 0;
|
|
}
|
|
|
|
sip = &Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index];
|
|
|
|
// if Pl_objp's target is not a big/capital ship, then cannot enter strafe mode
|
|
// AL 12-31-97: Even though transports are considered big ships, don't enter strafe mode on them
|
|
if ( !(sip->is_big_or_huge()) || (sip->flags[Ship::Info_Flags::Transport]) ) {
|
|
return 0;
|
|
}
|
|
|
|
// If Pl_objp not a fighter or bomber, don't enter strafe mode. -- MK, 11/11/97.
|
|
if ( !(Ship_info[Ships[pl_objp->instance].ship_info_index].is_fighter_bomber()) ) {
|
|
return 0;
|
|
}
|
|
|
|
Assert(weapon_objnum >= 0 && weapon_objnum < MAX_OBJECTS);
|
|
weapon_objp = &Objects[weapon_objnum];
|
|
Assert(weapon_objp->type == OBJ_WEAPON);
|
|
|
|
if (weapon_objp->parent < 0) {
|
|
return 0;
|
|
}
|
|
Assert(weapon_objp->parent < MAX_OBJECTS);
|
|
parent_objp = &Objects[weapon_objp->parent];
|
|
if ( (parent_objp->signature != weapon_objp->parent_sig) || (parent_objp->type != OBJ_SHIP) ) {
|
|
return 0;
|
|
}
|
|
|
|
// Maybe the ship which fired the weapon isn't the current target
|
|
if ( OBJ_INDEX(parent_objp) != aip->target_objnum ) {
|
|
|
|
//JAS IMPOSSIBLE if (1) { // consider_target_only ) {
|
|
//JAS IMPOSSIBLE return 0;
|
|
//JAS IMPOSSIBLE } else {
|
|
// switch targets
|
|
sip = &Ship_info[Ships[parent_objp->instance].ship_info_index];
|
|
if ( !(sip->is_big_or_huge()) || (sip->flags[Ship::Info_Flags::Transport]) ) {
|
|
return 0;
|
|
}
|
|
set_target_objnum(aip, OBJ_INDEX(parent_objp));
|
|
//JAS IMPOSSIBLE }
|
|
}
|
|
|
|
ai_big_strafe_maybe_attack_turret(pl_objp, weapon_objp);
|
|
|
|
// if we've got this far, the weapon must have come from the player's target, and it is a
|
|
// big/capital ship... so enter strafe mode
|
|
aip->previous_mode = aip->mode;
|
|
aip->mode = AIM_STRAFE;
|
|
aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
|
|
aip->submode = AIS_STRAFE_AVOID;
|
|
aip->submode_start_time = Missiontime;
|
|
// nprintf(("Alan","%s Accepted strafe mode\n", Ships[pl_objp->instance].ship_name));
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Consider attacking a turret, if a turret actually fired the weapon
|
|
// input: ship_objp => ship that will attack the turret
|
|
// weapon_objp =>
|
|
void ai_big_strafe_maybe_attack_turret(const object *ship_objp, const object *weapon_objp)
|
|
{
|
|
ai_info *aip;
|
|
object *parent_objp;
|
|
|
|
Assert(ship_objp->type == OBJ_SHIP);
|
|
aip = &Ai_info[Ships[ship_objp->instance].ai_index];
|
|
|
|
// Make decision to attack turret based on AI class. The better AI ships will realize that
|
|
// it is better to take out the turrets first on a big ship.
|
|
if ( (frand()*100) > (aip->ai_courage-15) )
|
|
return;
|
|
|
|
// If ship is already attacking a subsystem, don't switch
|
|
if ( aip->targeted_subsys != NULL ) {
|
|
return;
|
|
}
|
|
|
|
// If the weapon is not from a turret, return
|
|
if ( Weapons[weapon_objp->instance].turret_subsys == NULL ) {
|
|
return;
|
|
}
|
|
|
|
Assert(weapon_objp->parent >= 0 && weapon_objp->parent < MAX_OBJECTS);
|
|
parent_objp = &Objects[weapon_objp->parent];
|
|
|
|
// UnknownPlayer : Decide whether or not this weapon was a beam, in which case it might be a good
|
|
// idea to go after it even if its not on the current target. This is randomized so
|
|
// the ai will not always go after different ships firing beams at them.
|
|
// Approx 1/4 chance we'll go after the other ship's beam.
|
|
|
|
bool attack_turret_on_different_ship = (aip->ai_profile_flags[AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships])
|
|
&& (Weapon_info[Weapons[weapon_objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Beam]) && (frand()*100 < 25.0f);
|
|
|
|
// unless we're making an exception, we should only attack a turret if it sits on the current target
|
|
if ( !attack_turret_on_different_ship )
|
|
{
|
|
if ( (parent_objp->signature != weapon_objp->parent_sig) || (parent_objp->type != OBJ_SHIP) ) {
|
|
return;
|
|
}
|
|
|
|
if ( aip->target_objnum != OBJ_INDEX(parent_objp) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// attack the turret
|
|
set_targeted_subsys(aip, Weapons[weapon_objp->instance].turret_subsys, OBJ_INDEX(parent_objp));
|
|
}
|