1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12 #include "ai/aibig.h"
13 #include "globalincs/linklist.h"
14 #include "object/object.h"
15 #include "ship/ship.h"
16 #include "ship/afterburner.h"
17 #include "freespace2/freespace.h"
18 #include "weapon/weapon.h"
19 #include "io/timer.h"
20 #include "mission/missionparse.h"
21 #include "iff_defs/iff_defs.h"
22 #include "math/staticrand.h"
23 #include "ai/aigoals.h"
24
25
26
27 #pragma optimize("", off)
28 #pragma auto_inline(off)
29
30 #define SCAN_FIGHTERS_INTERVAL 2000 // how often an AI fighter/bomber should scan for enemy fighter/bombers
31 // if sitting still and pounding on a big ship. If enemy fighters are
32 // close ( < ENTER_STRAFE_THREAT_DIST ), then enter AIM_STRAFE
33
34 #define ENTER_STRAFE_THREAT_DIST_SQUARED 360000 // use squared distance, instead of 600
35
36 #define MIN_DOT_TO_ATTACK_SUBSYS 0.7f
37 #define MIN_DOT_TO_ATTACK_MOVING_SUBSYS 0.97f
38
39 // AI BIG MAGIC NUMBERS
40 #define STRAFE_RETREAT_COLLIDE_TIME 2.0 // when anticipated collision time is less than this, begin retreat
41 #define STRAFE_RETREAT_COLLIDE_DIST 100 // when perpendicular distance to *surface* is less than this, begin retreat
42 #define STRAFE_RETREAT_BOX_DIST 300 // distance beyond the bounding box to retreat
43 #define STRAFE_MAX_UNHIT_TIME 20 // Maximum amount of time to stay in strafe mode if not hit
44
45 #define EVADE_BOX_BASE_DISTANCE 300 // standard distance to end evade submode
46 #define EVADE_BOX_MIN_DISTANCE 200 // minimun distance to end evade submode, after long time
47
48 #define ATTACK_STOP_DISTANCE 150 // when distance to target is less than this, put on brakes
49
50 #define ATTACK_COLLIDE_BASE_DIST 300 // absolute distance at which to begin checking for possible collision
51 #define ATTACK_COLLIDE_AVOID_DIST 60 // perpendicular distance to attack surface at which begin avoiding
52 #define ATTACK_COLLIDE_AVOID_TIME 1.0 // anticipated collision time at which to begin evade
53 #define ATTACK_COLLIDE_SLOW_DIST 150 // perpendicular distance to attack surface at which begin slowing down
54 #define ATTACK_COLLIDE_SLOW_TIME 1.5 // anticipated collision time at which to begin slowing down
55
56 #define GLIDE_STRAFE_DISTANCE 50.0f //Distance from the ship to pass when glide strafing
57 #define GLIDE_STRAFE_MIN_TIME 2 //Minimum amount of time to stay on one glide strafe approach vector
58 #define GLIDE_STRAFE_MAX_TIME 15 //Maximum amount of time for each glide strafe pass
59
60 // forward declarations
61 void ai_big_evade_ship();
62 void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float dist_to_enemy, int modelnum);
63 void ai_big_avoid_ship();
64 int ai_big_maybe_follow_subsys_path(int do_dot_check=1);
65 void ai_big_strafe_position();
66
67 extern int model_which_octant_distant_many( vec3d *pnt, int model_num,matrix *model_orient, vec3d * model_pos, polymodel **pm, int *octs);
68 extern void compute_desired_rvec(vec3d *rvec, vec3d *goal_pos, vec3d *cur_pos);
69 extern void big_ship_collide_recover_start(object *objp, object *big_objp, vec3d *collide_pos, vec3d *collision_normal);
70
71
72 // Called by ai_big_pick_attack_point.
73 // Generates a random attack point.
74 // If truly_random flag set (haha), then generate a pretty random number. Otherwise, generate a static rand which
75 // tends to not change from frame to frame.
76 // Try four times and choose nearest point to increase chance of getting a good point.
ai_bpap(object * objp,vec3d * attacker_objp_pos,vec3d * attacker_objp_fvec,vec3d * attack_point,vec3d * local_attack_point,float fov,float weapon_travel_dist,vec3d * surface_normal,ship_subsys * ss)77 void ai_bpap(object *objp, vec3d *attacker_objp_pos, vec3d *attacker_objp_fvec, vec3d *attack_point, vec3d *local_attack_point, float fov, float weapon_travel_dist, vec3d *surface_normal, ship_subsys *ss)
78 {
79 float nearest_dist;
80 vec3d result_point, best_point;
81 vec3d rel_point;
82 int num_tries;
83 model_octant *octp;
84 polymodel *pm;
85 int i, q, octs[4];
86 ship_info *sip = &Ship_info[Ships[objp->instance].ship_info_index];
87 model_subsystem *tp = NULL;
88 if (ss != NULL)
89 tp = ss->system_info;
90
91 best_point = objp->pos;
92 nearest_dist = weapon_travel_dist;
93
94 model_which_octant_distant_many(attacker_objp_pos, sip->model_num, &objp->orient, &objp->pos, &pm, octs);
95
96 num_tries = (int) (vm_vec_dist(&objp->pos, attacker_objp_pos)/objp->radius);
97
98 if (num_tries >= 4)
99 num_tries = 1;
100 else
101 num_tries = 4 - num_tries;
102
103 // Index #0 is best one.
104 if ( pm->octants[octs[0]].verts ) {
105 *local_attack_point = *pm->octants[octs[0]].verts[0]; // Set just in case it doesn't get set below.
106 } else {
107 vm_vec_zero(local_attack_point);
108 }
109
110 for (q=0; q<4; q++) {
111 octp = &pm->octants[octs[q]];
112 if (octp->nverts > 0) {
113
114 if (num_tries > octp->nverts)
115 num_tries = octp->nverts;
116
117 if (num_tries > octp->nverts)
118 num_tries = octp->nverts;
119
120 for (i=0; i<num_tries; i++) {
121 int index;
122 float dist, dot;
123 vec3d v2p;
124
125 index = (int) (frand() * (octp->nverts));
126
127 rel_point = *octp->verts[index];
128 vm_vec_unrotate(&result_point, &rel_point, &objp->orient);
129 vm_vec_add2(&result_point, &objp->pos);
130
131 dist = vm_vec_normalized_dir(&v2p, &result_point, attacker_objp_pos);
132 bool in_fov = false;
133
134 dot = vm_vec_dot(&v2p, attacker_objp_fvec);
135 if (tp == NULL) {
136 if (dot > fov)
137 in_fov = true;
138 } else {
139 in_fov = turret_fov_test(ss, attacker_objp_fvec, &v2p);
140 }
141
142 if (in_fov) {
143 if (dist < nearest_dist) {
144 nearest_dist = dist;
145 best_point = result_point;
146 *local_attack_point = rel_point;
147 Assert( !vm_is_vec_nan(local_attack_point) );
148 if (dot > (1.0f + fov)/2.0f) // If this point is quite good, quit searching for a better one.
149 goto done_1;
150 }
151 }
152 }
153 }
154 }
155 done_1:
156
157 *attack_point = best_point;
158
159 // Cast from attack_objp_pos to local_attack_pos and check for nearest collision.
160 // If no collision, cast to (0,0,0) [center of big ship]** [best_point initialized to 000]
161
162 // do in world coords to get attack point, then translate to local for local_attack_point
163 vec3d attack_dir, end_point, temp;
164 float dist;
165 dist = vm_vec_normalized_dir(&attack_dir, attack_point, attacker_objp_pos);
166
167 if (dist > 0.1) {
168 vm_vec_scale_add(&end_point, attack_point, &attack_dir, 30.0f);
169 } else {
170 vm_vec_scale_add(&end_point, attack_point, attacker_objp_fvec, 30.0f);
171 }
172
173 mc_info mc;
174 mc_info_init(&mc);
175
176 mc.model_instance_num = Ships[objp->instance].model_instance_num;
177 mc.model_num = sip->model_num;
178 mc.orient = &objp->orient;
179 mc.pos = &objp->pos;
180 mc.p0 = attacker_objp_pos;
181 mc.p1 = &end_point;
182 mc.flags = MC_CHECK_MODEL;
183 mc.radius = 0.0f;
184 model_collide(&mc);
185
186 if (mc.num_hits > 0) {
187 *attack_point = mc.hit_point_world;
188 vm_vec_sub(&temp, attack_point, &objp->pos);
189 vm_vec_rotate(local_attack_point, &temp, &objp->orient);
190 if (surface_normal) {
191 vm_vec_unrotate(surface_normal, &mc.hit_normal, &objp->orient);
192 }
193 } else {
194 vm_vec_zero(local_attack_point);
195 *attack_point = objp->pos;
196 if (surface_normal) {
197 vm_vec_zero(surface_normal);
198 }
199 }
200 }
201
202 // Stuff a point to attack based on nearest octant.
203 // If no points in that octant, leave attack_point unmodified.
204 //
205 // Note: Default value for fov is 1.0f 1.0f means don't use fov parameter.
206 // If fov != 1.0f, try up to four times to find a point that's in the field of view.
ai_big_pick_attack_point_turret(object * objp,ship_subsys * ssp,vec3d * gpos,vec3d * gvec,vec3d * attack_point,float fov,float weapon_travel_dist)207 void ai_big_pick_attack_point_turret(object *objp, ship_subsys *ssp, vec3d *gpos, vec3d *gvec, vec3d *attack_point, float fov, float weapon_travel_dist)
208 {
209 if (!timestamp_elapsed(ssp->turret_pick_big_attack_point_timestamp)) {
210 vec3d result_point;
211 vm_vec_unrotate(&result_point, &ssp->turret_big_attack_point, &objp->orient);
212 vm_vec_add(attack_point, &result_point, &objp->pos);
213 } else {
214 vec3d local_attack_point;
215 ssp->turret_pick_big_attack_point_timestamp = timestamp(2000 + (int) (frand()*500.0f));
216 ai_bpap(objp, gpos, gvec, attack_point, &local_attack_point, fov, weapon_travel_dist, NULL, ssp);
217 ssp->turret_big_attack_point = local_attack_point;
218 }
219 }
220
221 // Stuff a point to attack based on nearest octant.
222 // If no points in that octant, leave attack_point unmodified.
223 //
224 // Note: Default value for fov is 1.0f 1.0f means don't use fov parameter.
225 // If fov != 1.0f, try up to four times to find a point that's in the field of view.
226 // Note, attacker_objp can be a ship or a weapon.
ai_big_pick_attack_point(object * objp,object * attacker_objp,vec3d * attack_point,float fov)227 void ai_big_pick_attack_point(object *objp, object *attacker_objp, vec3d *attack_point, float fov)
228 {
229 Assert(objp->instance > -1);
230 Assert(objp->type == OBJ_SHIP);
231
232 vec3d local_attack_point;
233
234 switch (attacker_objp->type) {
235 case OBJ_SHIP: {
236 ai_info *attacker_aip;
237 attacker_aip = &Ai_info[Ships[attacker_objp->instance].ai_index];
238 if (!timestamp_elapsed(attacker_aip->pick_big_attack_point_timestamp)) {
239 vec3d result_point;
240
241 vm_vec_unrotate(&result_point, &attacker_aip->big_attack_point, &objp->orient);
242 vm_vec_add(attack_point, &result_point, &objp->pos);
243
244 return;
245 }
246
247 attacker_aip->pick_big_attack_point_timestamp = timestamp(2000 + (int) (frand()*500.0f));
248 break;
249 }
250 case OBJ_WEAPON: {
251 weapon *wp = &Weapons[attacker_objp->instance];
252
253 if (!timestamp_elapsed(wp->pick_big_attack_point_timestamp)) {
254 vec3d result_point;
255
256 vm_vec_unrotate(&result_point, &wp->big_attack_point, &objp->orient);
257 vm_vec_add(attack_point, &result_point, &objp->pos);
258
259 return;
260 }
261 wp->pick_big_attack_point_timestamp = timestamp(2000 + (int) (frand()*500.0f));
262
263 break;
264 }
265 }
266
267 // checks valid line to target
268 vec3d surface_normal;
269 ai_bpap(objp, &attacker_objp->pos, &attacker_objp->orient.vec.fvec, attack_point, &local_attack_point, fov, 99999.9f, &surface_normal, NULL);
270
271 switch (attacker_objp->type) {
272 case OBJ_SHIP: {
273 ai_info *attacker_aip;
274 // if we can't find a new local_attack_point don't change local_attack_point
275 if (vm_vec_mag_squared(&local_attack_point) < 1) {
276 return;
277 }
278
279 attacker_aip = &Ai_info[Ships[attacker_objp->instance].ai_index];
280 attacker_aip->big_attack_point = local_attack_point;
281 attacker_aip->big_attack_surface_normal = surface_normal;
282 break;
283 }
284 case OBJ_WEAPON: {
285 weapon *wp = &Weapons[attacker_objp->instance];
286 wp->big_attack_point = local_attack_point;
287 Assert( !vm_is_vec_nan(&wp->big_attack_point) );
288 break;
289 }
290 }
291
292 return;
293 }
294
295 // Handler for SM_EVADE submode ( called from ai_big_chase() )
ai_big_evade_ship()296 void ai_big_evade_ship()
297 {
298 vec3d player_pos, enemy_pos;
299 float dist;
300 ship *shipp = &Ships[Pl_objp->instance];
301 ai_info *aip = &Ai_info[shipp->ai_index];
302 vec3d randvec, semi_enemy_pos;
303
304 ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
305
306 dist = vm_vec_dist_quick(&player_pos, &enemy_pos);
307 vm_vec_rand_vec_quick(&randvec);
308 if ((Missiontime>>14) & 1) {
309 vm_vec_scale_add(&semi_enemy_pos, &enemy_pos, &randvec, dist/2.0f);
310 aip->prev_goal_point = semi_enemy_pos;
311 } else {
312 semi_enemy_pos = aip->prev_goal_point;
313 }
314
315 accelerate_ship(aip, 1.0f - ((Missiontime>>8) & 0x3f)/128.0f );
316 turn_away_from_point(Pl_objp, &semi_enemy_pos, 0.0f);
317
318 float box_dist;
319 vec3d box_vec;
320 int is_inside;
321 box_dist = get_world_closest_box_point_with_delta(&box_vec, En_objp, &player_pos, &is_inside, EVADE_BOX_BASE_DISTANCE);
322 if (box_dist > EVADE_BOX_BASE_DISTANCE) {
323 aip->submode = SM_ATTACK;
324 aip->submode_start_time = Missiontime;
325 } else if ((box_dist > EVADE_BOX_MIN_DISTANCE) && (Missiontime - aip->submode_start_time > i2f(5)) ) {
326 aip->submode = SM_ATTACK;
327 aip->submode_start_time = Missiontime;
328 }
329
330 //TODO TEST
331
332 /* if (dist > 4*En_objp->radius) {
333 aip->submode = SM_ATTACK;
334 aip->submode_start_time = Missiontime;
335 } else if (dist > En_objp->radius) {
336 if (Missiontime - aip->submode_start_time > i2f(5)) {
337 aip->submode = SM_ATTACK;
338 aip->submode_start_time = Missiontime;
339 }
340 } */
341 }
342
343 // Handler for SM_AVOID submode ( called from ai_big_chase() )
ai_big_avoid_ship()344 void ai_big_avoid_ship()
345 {
346 ai_big_evade_ship();
347 }
348
349 // reset path following information
ai_big_subsys_path_cleanup(ai_info * aip)350 void ai_big_subsys_path_cleanup(ai_info *aip)
351 {
352 if ( aip->ai_flags & AIF_ON_SUBSYS_PATH ) {
353 aip->ai_flags &= ~AIF_ON_SUBSYS_PATH;
354 aip->path_goal_dist = -1;
355 aip->path_start = -1;
356 aip->path_cur = -1;
357 aip->path_length = 0;
358 }
359 }
360
361 // Maybe Pl_objp needs to follow a path to get in line-of-sight to a subsystem
362 // input: do_dot_check => default value 0, flag to indicate whether check should be done to ensure
363 // subsystem is within certain field of view. We don't want to check fov when
364 // strafing, since ship is weaving to avoid turret fire
ai_big_maybe_follow_subsys_path(int do_dot_check)365 int ai_big_maybe_follow_subsys_path(int do_dot_check)
366 {
367 ai_info *aip;
368 float dot = 1.0f, min_dot;
369 object *target_objp;
370
371 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
372 target_objp = &Objects[aip->target_objnum];
373
374 if ( (aip->targeted_subsys != NULL) && (aip->target_objnum >= 0) && (aip->targeted_subsys->system_info->path_num >= 0) ) {
375 int subsys_path_num, subsys_in_sight, checked_sight;
376 float dist;
377
378 // Get models of both source and target
379 polymodel *pm = model_get( Ship_info[Ships[Pl_objp->instance].ship_info_index].model_num );
380 polymodel *pm_t = model_get( Ship_info[Ships[target_objp->instance].ship_info_index].model_num );
381
382 // Necessary sanity check
383 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 );
384 if (aip->targeted_subsys->system_info->path_num >= pm_t->n_paths)
385 return 0;
386
387 // If attacking a subsystem, ensure that we have an unobstructed line of sight... if not, then move
388 // towards path linked to subsystem
389 subsys_in_sight = 0; // assume Pl_objp doesn't have line of sight to subys
390
391 // only check for subsystem sight every N milliseconds
392 checked_sight = 0;
393 if ( timestamp_elapsed(aip->path_subsystem_next_check) ) {
394 vec3d geye, gsubpos;
395 eye *ep;
396
397 aip->path_subsystem_next_check = timestamp(1500);
398
399 // get world pos of eye (stored in geye)
400 ep = &(pm->view_positions[Ships[Pl_objp->instance].current_viewpoint]);
401 model_find_world_point( &geye, &ep->pnt, pm->id, 0, &Pl_objp->orient, &Pl_objp->pos );
402
403 // get world pos of subsystem
404 vm_vec_unrotate(&gsubpos, &aip->targeted_subsys->system_info->pnt, &En_objp->orient);
405 vm_vec_add2(&gsubpos, &En_objp->pos);
406
407 checked_sight = 1;
408
409 // Calc dot between subsys normal (based on using path info), and subsys_to_eye vector. This is
410 // useful later when we want to decide whether we have a realistic line-of-sight to the subsystem.
411 vec3d subsys_normal, subsys_to_eye;
412 if ( do_dot_check ) {
413 if ( !ship_return_subsys_path_normal(&Ships[target_objp->instance], aip->targeted_subsys, &gsubpos, &subsys_normal) ) {
414 vm_vec_normalized_dir(&subsys_to_eye, &geye, &gsubpos);
415 dot = vm_vec_dot(&subsys_normal, &subsys_to_eye);
416 }
417 }
418
419 if ( ship_subsystem_in_sight(En_objp, aip->targeted_subsys, &geye, &gsubpos, 1) ) {
420 subsys_in_sight = 1;
421 }
422 }
423
424 // check if subsystem not in sight
425 min_dot = (target_objp->phys_info.fspeed > 5.0f?MIN_DOT_TO_ATTACK_MOVING_SUBSYS:MIN_DOT_TO_ATTACK_SUBSYS);
426 if ( (checked_sight && ((!subsys_in_sight) || (dot < min_dot)) ) ) {
427
428 aip->path_goal_dist = 5;
429 subsys_path_num = aip->targeted_subsys->system_info->path_num;
430 if ( ((aip->path_start) == -1 || (aip->mp_index != subsys_path_num)) && subsys_path_num < pm_t->n_paths ) {
431 // maybe create a new path
432 if ( subsys_path_num >= 0 ) {
433 Assert(aip->target_objnum >= 0);
434 ai_find_path(Pl_objp, aip->target_objnum, subsys_path_num, 0, 1);
435 if ( aip->path_start >= 0 ) {
436 aip->ai_flags |= AIF_ON_SUBSYS_PATH;
437 }
438 }
439 }
440 }
441
442 if ( checked_sight && subsys_in_sight && (dot > min_dot) ) {
443 // we've got a clear shot, stay here for a bit
444 aip->path_subsystem_next_check = timestamp_rand(5000,8000);
445 }
446
447 // If there is a path that we are tracking, see if ship has gotten to within
448 // aip->path_goal_dist units. If so, then ship can stop following the path. This
449 // is required since we don't want to follow the path all the way to the subsystem,
450 // and we want ships to stop different distances from their path destination
451 if ( aip->path_length > 0 ) {
452 int path_done=0;
453 int in_view=0;
454
455 Assert(aip->path_length >= 2);
456 dist = vm_vec_dist_quick(&Path_points[aip->path_start+aip->path_length-2].pos, &Pl_objp->pos);
457
458 if ( aip->path_cur >= (aip->path_start+aip->path_length-1) ) {
459 path_done=1;
460 }
461
462 min_dot = (target_objp->phys_info.fspeed > 5.0f?MIN_DOT_TO_ATTACK_MOVING_SUBSYS:MIN_DOT_TO_ATTACK_SUBSYS);
463 if ( (checked_sight && subsys_in_sight) && (dot > min_dot) ) {
464 in_view=1;
465 }
466
467 // if we've reached the destination, then we can stop following the path
468 if ( path_done || ( dist < aip->path_goal_dist ) || in_view ) {
469 ai_big_subsys_path_cleanup(aip);
470 } else if ( dist > aip->path_goal_dist ) {
471 // If we have a path to follow, follow it and return
472 if ( aip->path_start != -1 ) {
473 // for now, only follow the path to the first point
474 if ( aip->path_cur < (aip->path_start+aip->path_length-1) ) {
475 if ( aip->goal_objnum != aip->target_objnum ) {
476 aip->previous_mode = aip->mode;
477 aip->mode = AIM_NONE;
478 aip->submode = -1;
479 aip->submode_start_time = Missiontime;
480 return 1;
481 }
482 ai_path();
483 return 1;
484 }
485 }
486 }
487 }
488 }
489
490 return 0;
491 }
492
493 // This function is only called from ai_big_chase_attack() when a ship is flying very slowly and
494 // attacking a big ship. The ship should scan for enemy fighter/bombers... if any are close, then
495 // return 1, otherwise return 0;
496 //
497 // input: aip => ai_info pointer for Pl_objp
498 // sip => ship_info pointer for Pl_objp
499 //
500 // exit: 1 => ship should enter strafe mode
501 // 0 => ship should not change ai mode, no fighter/bomber threats are near
502 //
503 // NOTE: uses SCAN_FIGHTERS_INTERVAL and ENTER_STRAFE_THREAT_DIST_SQUARED which are defined in AiBig.h
ai_big_maybe_start_strafe(ai_info * aip,ship_info * sip)504 int ai_big_maybe_start_strafe(ai_info *aip, ship_info *sip)
505 {
506 // if moving slowly (or stopped), and SIF_SMALL_SHIP, then enter STRAFE mode if enemy fighter/bombers
507 // are near
508 if ( sip->flags & SIF_SMALL_SHIP ) {
509 if ( timestamp_elapsed(aip->scan_for_enemy_timestamp) ) {
510 ship_obj *so;
511 object *test_objp;
512 ship *test_sp;
513 float dist_squared;
514
515 aip->scan_for_enemy_timestamp = timestamp(SCAN_FIGHTERS_INTERVAL);
516 // iterate through ships, and see if any fighter/bomber from opposite team are near
517 so = GET_FIRST(&Ship_obj_list);
518 while( so != END_OF_LIST(&Ship_obj_list) ) {
519 test_objp = &Objects[so->objnum];
520 test_sp = &Ships[test_objp->instance];
521
522 if ( iff_x_attacks_y(Ships[Pl_objp->instance].team, test_sp->team) ) {
523 if ( Ship_info[test_sp->ship_info_index].flags & SIF_SMALL_SHIP ) {
524 dist_squared = vm_vec_dist_squared(&Pl_objp->pos, &test_objp->pos);
525 if ( dist_squared < ENTER_STRAFE_THREAT_DIST_SQUARED ) {
526 return 1;
527 }
528 }
529 }
530 so = GET_NEXT(so);
531 }
532 }
533 }
534 // If we've reached here, there are no enemy fighter/bombers near
535 return 0;
536 }
537
538 // ATTACK submode handler for chase mode.
ai_big_chase_attack(ai_info * aip,ship_info * sip,vec3d * enemy_pos,float dist_to_enemy,int modelnum)539 void ai_big_chase_attack(ai_info *aip, ship_info *sip, vec3d *enemy_pos, float dist_to_enemy, int modelnum)
540 {
541 int start_bank;
542 float dot_to_enemy, time_to_hit;
543 polymodel *po = model_get( modelnum );
544
545 // Maybe evade an incoming weapon.
546 if (((time_to_hit = ai_endangered_by_weapon(aip)) < 4.0f) && (time_to_hit >= 0.0f)) {
547 aip->submode = SM_EVADE_WEAPON;
548 aip->submode_start_time = Missiontime;
549 aip->prev_goal_point = En_objp->pos;
550 } else {
551 // If moving slowly, maybe evade incoming fire.
552 if (Pl_objp->phys_info.speed < 3.0f) {
553 object *objp;
554 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
555 if ((objp->type == OBJ_WEAPON) && (iff_x_attacks_y(Ships[Pl_objp->instance].team, Weapons[objp->instance].team)))
556 if (Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_LASER) {
557 vec3d in_vec;
558 float dist;
559
560 vm_vec_sub(&in_vec, &objp->pos, &Pl_objp->pos);
561 if (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.0f) {
562 dist = vm_vec_normalize(&in_vec);
563 if ((dist < 200.0f) && (vm_vec_dot(&in_vec, &objp->orient.vec.fvec) > 0.95f)) {
564 if ((Objects[objp->parent].signature == objp->parent_sig) && (vm_vec_dist_quick(&objp->pos, &Objects[objp->parent].pos) < 300.0f)) {
565 set_target_objnum(aip, objp->parent);
566 aip->submode = SM_ATTACK;
567 aip->submode_start_time = Missiontime;
568 } else {
569 aip->submode = SM_EVADE;
570 aip->submode_start_time = Missiontime;
571 aip->prev_goal_point = En_objp->pos;
572 }
573 }
574 }
575 }
576 }
577
578 // Since ship is moving slowly and attacking a large ship, scan if enemy fighters are near, if so
579 // then enter strafe mode
580 if ( ai_big_maybe_start_strafe(aip, sip) ) {
581 aip->previous_mode = aip->mode;
582 aip->mode = AIM_STRAFE;
583 aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
584 ai_big_strafe_position();
585 return;
586 }
587
588 } // end if ( Pl_objp->phys_info.speed < 3.0f )
589
590 //Maybe enter glide strafe (check every 8 seconds, on a different schedule for each ship)
591 if ((sip->can_glide == true) && !(aip->ai_flags & AIF_KAMIKAZE) && static_randf((Missiontime + static_rand(aip->shipnum)) >> 19) < aip->ai_glide_strafe_percent) {
592 aip->previous_mode = aip->mode;
593 aip->mode = AIM_STRAFE;
594 aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
595 aip->submode = AIS_STRAFE_GLIDE_ATTACK;
596 aip->submode_parm1 = 0;
597 aip->submode_start_time = Missiontime;
598 }
599
600 // see if Pl_objp needs to reposition to get a good shot at subsystem which is being attacked
601 if ( ai_big_maybe_follow_subsys_path() ) {
602 if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
603 afterburners_stop(Pl_objp);
604 }
605 return;
606 }
607
608 vec3d *rel_pos, vec_to_enemy;
609 float weapon_travel_dist;
610
611 start_bank = Ships[aip->shipnum].weapons.current_primary_bank;
612
613 if ((po->n_guns) && (start_bank != -1)) {
614 rel_pos = &po->gun_banks[start_bank].pnt[0];
615 } else
616 rel_pos = NULL;
617
618 dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, enemy_pos, &Pl_objp->pos);
619 dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
620
621 vec3d rvec_vec, *rvec = &rvec_vec;
622
623 if (dist_to_enemy > 500.0f)
624 compute_desired_rvec(rvec, enemy_pos, &Pl_objp->pos);
625 else
626 rvec = NULL;
627
628 ai_turn_towards_vector(enemy_pos, Pl_objp, flFrametime, sip->srotation_time, NULL, rel_pos, 0.0f, 0, rvec);
629
630 // calc range of primary weapon
631 weapon_travel_dist = ai_get_weapon_dist(&Ships[Pl_objp->instance].weapons);
632
633 if ( aip->targeted_subsys != NULL ) {
634 if (dist_to_enemy > (weapon_travel_dist-20)) {
635 accelerate_ship(aip, 1.0f);
636
637 ship *shipp = &Ships[Pl_objp->instance];
638 if ((aip->ai_flags & AIF_FREE_AFTERBURNER_USE) && !(shipp->flags2 & SF2_AFTERBURNER_LOCKED) && (dot_to_enemy > 0.75f)) {
639 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
640 afterburners_start(Pl_objp);
641 aip->afterburner_stop_time = Missiontime + 3*F1_0;
642 }
643 } else if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
644 afterburners_stop(Pl_objp);
645 }
646 } else {
647 // AL 12-31-97: Move at least as quickly as your target is moving...
648 accelerate_ship(aip, MAX(1.0f - dot_to_enemy, Objects[aip->target_objnum].phys_info.fspeed/sip->max_speed));
649
650 if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
651 afterburners_stop(Pl_objp);
652 }
653 }
654
655 } else {
656 float accel;
657 if (dot_to_enemy < 0.0f) {
658 accel = 0.5f;
659 } else if (dot_to_enemy < 0.75f) {
660 accel = 0.75f;
661 } else {
662 if (dist_to_enemy > weapon_travel_dist/2.0f) {
663 accel = 1.0f;
664 } else {
665 accel = 1.0f - dot_to_enemy;
666 }
667 }
668
669 // use dist normal to enemy here (don't break 50 barrier)
670 if (dist_to_enemy < ATTACK_STOP_DISTANCE) {
671 // accelerate_ship(aip, accel * 0.5f);
672 accelerate_ship(aip, -1.0f);
673 if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
674 afterburners_stop(Pl_objp);
675 }
676 } else {
677 accelerate_ship(aip, accel);
678
679 ship *shipp = &Ships[Pl_objp->instance];
680 if ((aip->ai_flags & AIF_FREE_AFTERBURNER_USE) && !(shipp->flags2 & SF2_AFTERBURNER_LOCKED) && (accel > 0.95f)) {
681 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
682 afterburners_start(Pl_objp);
683 aip->afterburner_stop_time = Missiontime + 3*F1_0;
684 }
685 } else if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
686 afterburners_stop(Pl_objp);
687 }
688 }
689 }
690 }
691 }
692
693 // Handler for submode SM_CONTINUOUS_TURN
ai_big_chase_ct()694 void ai_big_chase_ct()
695 {
696 ai_chase_ct();
697 }
698
699 extern void ai_select_secondary_weapon(object *objp, ship_weapon *swp, int priority1 = -1, int priority2 = -1);
700 extern float set_secondary_fire_delay(ai_info *aip, ship *shipp, weapon_info *swip, bool burst);
701 extern void ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp);
702 extern int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time);
703
704 extern void maybe_cheat_fire_synaptic(object *objp, ai_info *aip);
705
706 // Determine if Pl_objp should fire weapons at current target, based on input parameters
707 //
708 // dist_to_enemy => distance (in m) to attack point on current target
709 // dot_to_enemy => dot product between fvec of Pl_objp and vector from Pl_objp to attack point
710 //
ai_big_maybe_fire_weapons(float dist_to_enemy,float dot_to_enemy,vec3d * firing_pos,vec3d * enemy_pos,vec3d * enemy_vel)711 void ai_big_maybe_fire_weapons(float dist_to_enemy, float dot_to_enemy, vec3d *firing_pos, vec3d *enemy_pos, vec3d *enemy_vel)
712 {
713 ai_info *aip;
714 ship_weapon *swp;
715 int has_fired = -1;
716
717 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
718 swp = &Ships[Pl_objp->instance].weapons;
719
720 if (dot_to_enemy > 0.95f - 0.5f * En_objp->radius/MAX(1.0f, En_objp->radius + dist_to_enemy)) {
721 aip->time_enemy_in_range += flFrametime;
722
723 // Chance of hitting ship is based on dot product of firing ship's forward vector with vector to ship
724 // and also the size of the target relative to distance to target.
725 if (dot_to_enemy > MAX(0.5f, 0.90f + aip->ai_accuracy/10.0f - En_objp->radius/MAX(1.0f,dist_to_enemy))) {
726
727 ship *temp_shipp;
728 temp_shipp = &Ships[Pl_objp->instance];
729 ship_weapon *tswp = &temp_shipp->weapons;
730
731 if ( tswp->num_primary_banks > 0 ) {
732 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);
733 weapon_info *wip = &Weapon_info[tswp->primary_bank_weapons[tswp->current_primary_bank]];
734
735 if (dist_to_enemy < MIN((wip->max_speed * wip->lifetime), wip->weapon_range)){
736 has_fired = 1;
737 if(! ai_fire_primary_weapon(Pl_objp)){
738 has_fired = -1;
739 // ship_stop_fire_primary(Pl_objp);
740 }
741 }
742 }
743
744 if (tswp->num_secondary_banks > 0) {
745
746 int priority1, priority2;
747
748 priority1 = -1;
749 priority2 = -1;
750
751 // Maybe favor selecting a bomb.
752 // Note, if you're firing a bomb, if it's aspect seeking, the firing conditions can be looser.
753 if (Ship_info[Ships[En_objp->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP))
754 if (En_objp->phys_info.speed * dist_to_enemy < 5000.0f) // Don't select a bomb if enemy moving fast relative to distance
755 priority1 = WIF_BOMB;
756
757 if (!(En_objp->flags & OF_PROTECTED) || (aip->goals[0].ai_mode & (AI_GOAL_DISABLE_SHIP | AI_GOAL_DISARM_SHIP))) {
758 //ai_select_secondary_weapon(Pl_objp, tswp, priority1, priority2); // Note, need to select to get weapon speed and lifetime
759 if(aip->goals[0].ai_mode & (AI_GOAL_DISABLE_SHIP | AI_GOAL_DISARM_SHIP)) {
760 priority1 = WIF_PUNCTURE;
761 }
762 ai_choose_secondary_weapon(Pl_objp, aip, En_objp);
763 int current_bank = tswp->current_secondary_bank;
764 weapon_info *swip = &Weapon_info[tswp->secondary_bank_weapons[current_bank]];
765
766 if(!(En_objp->flags & OF_PROTECTED) || ((aip->goals[0].ai_mode & (AI_GOAL_DISABLE_SHIP | AI_GOAL_DISARM_SHIP)) && swip->wi_flags & WIF_PUNCTURE )) { //override lockdown on protected ships when using anti subsystem weapons - Valathil
767 // If ship is protected and very low on hits, don't fire missiles.
768 if ((current_bank > -1) && (!(En_objp->flags & OF_PROTECTED) || (En_objp->hull_strength > 10*swip->damage))) {
769 if (aip->ai_flags & AIF_UNLOAD_SECONDARIES) {
770 if (timestamp_until(swp->next_secondary_fire_stamp[current_bank]) > swip->fire_wait*1000.0f) {
771 swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (swip->fire_wait*1000.0f));
772 }
773 }
774
775 if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank])) {
776 float firing_range;
777 if (swip->wi_flags2 & WIF2_LOCAL_SSM)
778 firing_range=swip->lssm_lock_range;
779 else
780 firing_range = MIN((swip->max_speed * swip->lifetime), swip->weapon_range);
781 // reduce firing range of secondaries in nebula
782 extern int Nebula_sec_range;
783 if ((The_mission.flags & MISSION_FLAG_FULLNEB) && Nebula_sec_range) {
784 firing_range *= 0.8f;
785 }
786
787 float t = 0.25f; // default delay in seconds until next fire.
788
789 if (dist_to_enemy < firing_range*1.0f) {
790
791
792 //vm_vec_scale_add(&future_enemy_pos, enemy_pos, enemy_vel, dist_to_enemy/swip->max_speed);
793 //if (vm_vec_dist_quick(&future_enemy_pos, firing_pos) < firing_range * 0.8f) {
794 if (ai_fire_secondary_weapon(Pl_objp)) {
795
796 int current_bank_adjusted = MAX_SHIP_PRIMARY_BANKS + current_bank;
797
798 if ((aip->ai_flags & AIF_UNLOAD_SECONDARIES) || (swip->burst_flags & WBF_FAST_FIRING)) {
799 if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
800 t = swip->burst_delay;
801 swp->burst_counter[current_bank_adjusted]++;
802 } else {
803 t = swip->fire_wait;
804 if ((swip->burst_shots > 0) && (swip->burst_flags & WBF_RANDOM_LENGTH)) {
805 swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
806 } else {
807 swp->burst_counter[current_bank_adjusted] = 0;
808 }
809 }
810 } else {
811 if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
812 t = set_secondary_fire_delay(aip, temp_shipp, swip, true);
813 swp->burst_counter[current_bank_adjusted]++;
814 } else {
815 t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
816 if ((swip->burst_shots > 0) && (swip->burst_flags & WBF_RANDOM_LENGTH)) {
817 swp->burst_counter[current_bank_adjusted] = myrand() % swip->burst_shots;
818 } else {
819 swp->burst_counter[current_bank_adjusted] = 0;
820 }
821 }
822 }
823 swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
824 }
825 //}
826 }
827 swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
828 }
829 }
830 }
831 }
832 }
833 }
834 } else {
835 if (flFrametime < 1.0f)
836 aip->time_enemy_in_range *= (1.0f - flFrametime);
837 else
838 aip->time_enemy_in_range = 0;
839 }
840
841 if(has_fired == -1){ //stuff that hapens when the ship stops fireing
842 ship_stop_fire_primary(Pl_objp);
843 }
844
845 }
846
847 // switch ai ship into chase mode
ai_big_switch_to_chase_mode(ai_info * aip)848 void ai_big_switch_to_chase_mode(ai_info *aip)
849 {
850 aip->previous_mode = aip->mode;
851 aip->mode = AIM_CHASE;
852 aip->submode = SM_ATTACK;
853 aip->submode_start_time = Missiontime;
854 }
855
856 extern int ai_big_strafe_maybe_retreat(float dist, vec3d *target_pos);
857
858 // Make object Pl_objp chase object En_objp, which is a big ship, not a small ship.
ai_big_chase()859 void ai_big_chase()
860 {
861 float dist_to_enemy, dot_to_enemy;
862 vec3d player_pos, enemy_pos, vec_to_enemy;
863 ship_info *sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
864 ship *shipp = &Ships[Pl_objp->instance];
865 ai_info *aip = &Ai_info[shipp->ai_index];
866 vec3d predicted_enemy_pos;
867
868 Assert(aip->mode == AIM_CHASE);
869
870 maybe_cheat_fire_synaptic(Pl_objp, aip);
871
872 ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
873
874 player_pos = Pl_objp->pos;
875 ai_big_pick_attack_point(En_objp, Pl_objp, &enemy_pos, 0.8f);
876
877 // Compute the predicted position of the center of the ship, then add the delta to the goal pos.
878 if (En_objp->phys_info.speed > 3.0f) {
879 set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &En_objp->pos, &En_objp->phys_info.vel, aip);
880 vm_vec_add2(&enemy_pos, &predicted_enemy_pos);
881 vm_vec_sub2(&enemy_pos, &En_objp->pos);
882 } else
883 predicted_enemy_pos = En_objp->pos;
884
885 if (aip->targeted_subsys != NULL) {
886 get_subsystem_pos(&enemy_pos, En_objp, aip->targeted_subsys);
887 }
888
889 dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, &enemy_pos, &player_pos); // - En_objp->radius;
890 dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
891
892 if (aip->ai_flags & AIF_TARGET_COLLISION) {
893 if ( ai_big_strafe_maybe_retreat(dist_to_enemy, &enemy_pos) ) {
894 aip->mode = AIM_STRAFE;
895 aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
896 aip->submode = AIS_STRAFE_AVOID;
897 aip->submode_start_time = Missiontime;
898 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
899 afterburners_stop(Pl_objp);
900 }
901 return;
902 }
903 }
904
905 if (aip->ai_flags & AIF_KAMIKAZE) {
906 //nprintf(("AI", "Kamikaze: %7.3f %7.3f\n", dot_to_enemy, dist_to_enemy));
907 accelerate_ship(aip, 1.0f);
908 if ((dist_to_enemy < 400.0f) && ai_maybe_fire_afterburner(Pl_objp, aip)) {
909 afterburners_start(Pl_objp);
910 aip->afterburner_stop_time = Missiontime + 3*F1_0;
911 }
912 }
913
914 // If just acquired target, or target is not in reasonable cone, don't refine believed enemy position.
915 if ((dot_to_enemy < 0.25f) || (aip->target_time < 1.0f) || (aip->ai_flags & AIF_SEEK_LOCK)) {
916 update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
917 } else if (aip->targeted_subsys != NULL) {
918 Assert(aip->targeted_subsys != NULL);
919 get_subsystem_pos(&enemy_pos, En_objp, aip->targeted_subsys);
920 vm_vec_add2(&enemy_pos, &predicted_enemy_pos);
921 vm_vec_sub2(&enemy_pos, &En_objp->pos);
922 dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, &enemy_pos, &player_pos); // - En_objp->radius;
923 dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
924 update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy, En_objp->radius);
925 } else if (En_objp->flags & OF_PROTECTED) { // If protected and we're not attacking a subsystem, stop attacking!
926 update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
927 aip->target_objnum = -1;
928 if (find_enemy(Pl_objp-Objects, MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level]) == -1) {
929 ai_do_default_behavior(Pl_objp);
930 return;
931 }
932 } else {
933 update_aspect_lock_information(aip, &vec_to_enemy, dist_to_enemy - En_objp->radius, En_objp->radius);
934 }
935
936 // If recently acquired target and not looking at target, just turn a bit.
937 switch (aip->submode) {
938 case SM_ATTACK:
939 case SM_SUPER_ATTACK:
940 if (vm_vec_dist_quick(&Pl_objp->pos, &predicted_enemy_pos) > 100.0f + En_objp->radius * 2.0f) {
941 if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &predicted_enemy_pos, 10.0f)) {
942 if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
943 afterburners_stop(Pl_objp);
944 }
945 return;
946 }
947 }
948
949 if (aip->target_time < 2.0f)
950 if ((dot_to_enemy < 0.9f) || (dist_to_enemy > 300.0f)) {
951 aip->submode = SM_CONTINUOUS_TURN;
952 aip->submode_start_time = Missiontime - fl2f(2.75f); // This backdated start time allows immediate switchout.
953 if (dot_to_enemy > 0.0f)
954 aip->target_time += flFrametime * dot_to_enemy;
955 }
956 break;
957 }
958
959 //
960 // Set turn and acceleration based on submode.
961 //
962
963 if (((aip->submode != SM_ATTACK) && (aip->submode != SM_SUPER_ATTACK) && (aip->submode != SM_ATTACK_FOREVER) && (aip->submode != SM_EVADE_WEAPON)) || (aip->mode == AIM_STRAFE)){
964 if ((Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && !(aip->ai_flags & AIF_KAMIKAZE)) {
965 afterburners_stop(Pl_objp);
966 }
967 }
968
969 switch (aip->submode) {
970 case SM_CONTINUOUS_TURN:
971 ai_big_chase_ct();
972 break;
973
974 case SM_ATTACK:
975 case SM_SUPER_ATTACK:
976 case SM_ATTACK_FOREVER:
977 ai_big_chase_attack(aip, sip, &enemy_pos, dist_to_enemy, sip->model_num);
978 break;
979
980 case SM_EVADE:
981 ai_big_evade_ship();
982 break;
983
984 case SM_AVOID:
985 ai_big_avoid_ship();
986 break;
987
988 case SM_EVADE_WEAPON:
989 evade_weapon();
990 break;
991
992 default:
993 aip->last_attack_time = Missiontime;
994 aip->submode = SM_ATTACK;
995 aip->submode_start_time = Missiontime;
996 break;
997 }
998
999 // maybe Pl_objp was forced into strafe mode, if so return now
1000 if ( aip->mode == AIM_STRAFE )
1001 return;
1002
1003 // Maybe choose a new submode.
1004 if (aip->submode != SM_AVOID && aip->submode != SM_EVADE ) {
1005 // If a very long time since attacked, attack no matter what!
1006 if (aip->submode != SM_SUPER_ATTACK) {
1007 if (Missiontime - aip->last_attack_time > i2f(6)) {
1008 aip->submode = SM_SUPER_ATTACK;
1009 aip->submode_start_time = Missiontime;
1010 aip->last_attack_time = Missiontime;
1011 }
1012 }
1013
1014 // If a collision is expected, pull out!
1015 float dist_normal_to_enemy;
1016
1017 if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
1018 dist_normal_to_enemy = fl_abs((dist_to_enemy * vm_vec_dot(&vec_to_enemy, &aip->big_attack_surface_normal)));
1019 } else {
1020 // don;t have normal so use a conservative value here
1021 dist_normal_to_enemy = 0.3f * dist_to_enemy;
1022 }
1023
1024 // 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));
1025 // if (Framecount % 30 == 1) {
1026 // mprintf(("normal dist; %.1f, time: %.1f\n", dist_normal_to_enemy, time_to_enemy));
1027 // }
1028
1029 // 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)
1030 // this will allow us to get closer on a bad normal
1031 dist_normal_to_enemy = MAX(0.3f*dist_to_enemy, dist_normal_to_enemy);
1032
1033 if (dist_to_enemy < ATTACK_COLLIDE_BASE_DIST) {
1034 // within 50m or 1sec
1035 float time_to_enemy;
1036 if (vm_vec_mag_squared(&aip->big_attack_surface_normal) > 0.9) {
1037 if (Pl_objp->phys_info.speed > 0.1) {
1038 time_to_enemy = dist_normal_to_enemy / fl_abs(vm_vec_dot(&Pl_objp->phys_info.vel, &aip->big_attack_surface_normal));
1039 } else {
1040 // a big time
1041 time_to_enemy = 100.0f;
1042 }
1043 } else {
1044 if (Pl_objp->phys_info.speed > 0.1) {
1045 time_to_enemy = dist_normal_to_enemy / Pl_objp->phys_info.speed;
1046 } else {
1047 // a big time
1048 time_to_enemy = 100.0f;
1049 }
1050 }
1051
1052 float speed_dist = MAX(0.0f, (Pl_objp->phys_info.speed-50) * 2);
1053 if ((dist_normal_to_enemy < ATTACK_COLLIDE_AVOID_DIST + speed_dist) || (time_to_enemy < ATTACK_COLLIDE_AVOID_TIME) ) {
1054 // get away, simulate crsh recovery (don't use avoid)
1055 // accelerate_ship(aip, -1.0f);
1056 big_ship_collide_recover_start(Pl_objp, En_objp, &Pl_objp->pos, NULL);
1057 // aip->submode = SM_AVOID;
1058 // aip->submode_start_time = Missiontime;
1059 } else if ((dist_normal_to_enemy < ATTACK_COLLIDE_SLOW_DIST) || (time_to_enemy < ATTACK_COLLIDE_SLOW_TIME) ) {
1060 // slow down
1061 accelerate_ship(aip, -1.0f);
1062 }
1063 }
1064
1065 /*
1066 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)) {
1067 if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, (aip->targeted_subsys == NULL)*2.0f + 1.5f)) {
1068 if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
1069 accelerate_ship(aip, -1.0f);
1070 } else {
1071 aip->submode = SM_AVOID;
1072 aip->submode_start_time = Missiontime;
1073 }
1074 }
1075 } */
1076 }
1077
1078 switch (aip->submode) {
1079 case SM_CONTINUOUS_TURN:
1080 if (Missiontime - aip->submode_start_time > i2f(3)) {
1081 aip->last_attack_time = Missiontime;
1082 aip->submode = SM_ATTACK;
1083 aip->submode_start_time = Missiontime;
1084 }
1085 break;
1086
1087 case SM_ATTACK:
1088 case SM_ATTACK_FOREVER:
1089 break;
1090
1091 case SM_EVADE:
1092 if (dist_to_enemy > 4*En_objp->radius) {
1093 aip->submode = SM_ATTACK;
1094 aip->submode_start_time = Missiontime;
1095 aip->last_attack_time = Missiontime;
1096 }
1097 break;
1098
1099 case SM_SUPER_ATTACK:
1100 break;
1101
1102 case SM_AVOID:
1103 // if outside box by > 300 m
1104 // DA 4/20/98 can never get here attacking a big ship
1105 {
1106 int is_inside;
1107 float box_dist = get_world_closest_box_point_with_delta(NULL, En_objp, &Pl_objp->pos, &is_inside, 0.0f);
1108
1109 if (box_dist > 300) {
1110 aip->submode = SM_ATTACK;
1111 aip->submode_start_time = Missiontime;
1112 aip->last_attack_time = Missiontime;
1113 }
1114 }
1115 break;
1116
1117 /* if (dot_to_enemy > -0.2f) {
1118 aip->submode_start_time = Missiontime;
1119 } else if (Missiontime - aip->submode_start_time > i2f(1)/2) {
1120 if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, 3.0f)) {
1121 aip->submode_start_time = Missiontime;
1122 } else {
1123 aip->submode = SM_ATTACK;
1124 aip->submode_start_time = Missiontime;
1125 aip->last_attack_time = Missiontime;
1126 }
1127 }
1128
1129 break;*/
1130
1131 case SM_EVADE_WEAPON:
1132 if (aip->danger_weapon_objnum == -1) {
1133 aip->submode = SM_ATTACK;
1134 aip->submode_start_time = Missiontime;
1135 aip->last_attack_time = Missiontime;
1136 }
1137 break;
1138
1139 default:
1140 aip->submode = SM_ATTACK;
1141 aip->submode_start_time = Missiontime;
1142 aip->last_attack_time = Missiontime;
1143 }
1144
1145 //
1146 // Maybe fire primary weapon and update time_enemy_in_range
1147 //
1148 //nprintf(("AI", "time_enemy_in_range = %7.3f, dot = %7.3f\n", aip->time_enemy_in_range, dot_to_enemy));
1149
1150 // AL: add condition that Pl_objp must not be following a path to fire. This may be too extreme, but
1151 // I noticed AI ships firing inappropriately when following a path near a big ship.
1152 // TODO: investigate why ships fire (and aren't close to hitting ship) when following a path near
1153 // a big ship
1154 if (aip->mode != AIM_EVADE && aip->path_start == -1 ) {
1155 ai_big_maybe_fire_weapons(dist_to_enemy, dot_to_enemy, &player_pos, &predicted_enemy_pos, &En_objp->phys_info.vel);
1156 } else {
1157 if (flFrametime < 1.0f)
1158 aip->time_enemy_in_range *= (1.0f - flFrametime);
1159 else
1160 aip->time_enemy_in_range = 0;
1161 }
1162 }
1163
ai_big_ship(object * objp)1164 void ai_big_ship(object *objp)
1165 {
1166 // do nothing
1167 }
1168
1169 // all three parms are output parameters
1170 // Enemy object is En_objp
1171 // pos => world pos of attack point
1172 // dist => distance from Pl_objp front to attack point
1173 // dot => dot of Pl_objp fvec and vector to attack point
1174 // fire_pos => world pos from which firing
ai_big_attack_get_data(vec3d * enemy_pos,float * dist_to_enemy,float * dot_to_enemy)1175 void ai_big_attack_get_data(vec3d *enemy_pos, float *dist_to_enemy, float *dot_to_enemy)
1176 {
1177 vec3d player_pos, vec_to_enemy, predicted_enemy_pos;
1178 ship *shipp = &Ships[Pl_objp->instance];
1179 ai_info *aip = &Ai_info[shipp->ai_index];
1180 ship_info *esip = &Ship_info[Ships[En_objp->instance].ship_info_index];
1181
1182 Assert(aip->mode == AIM_STRAFE);
1183
1184 // ensure that Pl_objp is still targeting a big ship
1185 if ( !(esip->flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) ) {
1186 ai_big_switch_to_chase_mode(aip);
1187 return;
1188 }
1189
1190 ai_set_positions(Pl_objp, En_objp, aip, &player_pos, enemy_pos);
1191
1192 player_pos = Pl_objp->pos;
1193
1194 if (aip->targeted_subsys != NULL) {
1195 Assert(aip->targeted_subsys != NULL);
1196 get_subsystem_pos(enemy_pos, En_objp, aip->targeted_subsys);
1197 } else {
1198 // checks valid line to target
1199 ai_big_pick_attack_point(En_objp, Pl_objp, enemy_pos, 0.8f);
1200 }
1201
1202 // Take player pos to be center of ship + ship_radius
1203 vm_vec_scale_add2(&player_pos, &Pl_objp->orient.vec.fvec, Pl_objp->radius);
1204
1205 // If seeking lock, try to point directly at ship, else predict position so lasers can hit it.
1206 // If just acquired target, or target is not in reasonable cone, don't refine believed enemy position.
1207 vm_vec_normalized_dir(&vec_to_enemy, enemy_pos, &player_pos);
1208 *dot_to_enemy=vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
1209 if ((*dot_to_enemy < 0.25f) || (aip->target_time < 1.0f) || (aip->ai_flags & AIF_SEEK_LOCK)) {
1210 predicted_enemy_pos=*enemy_pos;
1211 } else {
1212 vec3d gun_pos, pnt;
1213 polymodel *po = model_get( Ship_info[shipp->ship_info_index].model_num );
1214 float weapon_speed;
1215
1216 // Compute position of gun in absolute space and use that as fire position.
1217 if (po->n_guns > 0 && !(The_mission.ai_profile->flags2 & AIPF2_AI_AIMS_FROM_SHIP_CENTER)) {
1218 pnt = po->gun_banks[0].pnt[0];
1219 vm_vec_unrotate(&gun_pos, &pnt, &Pl_objp->orient);
1220 vm_vec_add2(&gun_pos, &Pl_objp->pos);
1221 } else {
1222 //Use the convergence offset, if there is one
1223 vm_vec_copy_scale(&pnt, &Ship_info[shipp->ship_info_index].convergence_offset, 1.0f);
1224 }
1225 weapon_speed = ai_get_weapon_speed(&shipp->weapons);
1226
1227 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);
1228 }
1229
1230 *dist_to_enemy = vm_vec_normalized_dir(&vec_to_enemy, &predicted_enemy_pos, &player_pos);
1231 *dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
1232 update_aspect_lock_information(aip, &vec_to_enemy, *dist_to_enemy, En_objp->radius);
1233
1234 *enemy_pos = predicted_enemy_pos;
1235 }
1236
1237 // check to see if Pl_objp has gotten too close to attacking point.. if so, break off by entering
1238 // AIS_STRAFE_RETREAT
ai_big_strafe_maybe_retreat(float dist,vec3d * target_pos)1239 int ai_big_strafe_maybe_retreat(float dist, vec3d *target_pos)
1240 {
1241 ai_info *aip;
1242 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1243
1244 vec3d vec_to_target;
1245 vm_vec_sub(&vec_to_target, target_pos, &Pl_objp->pos);
1246
1247 float dist_to_target, dist_normal_to_target, time_to_target;
1248 dist_to_target = vm_vec_mag_quick(&vec_to_target);
1249 if (vm_vec_mag_quick(&aip->big_attack_surface_normal) > 0.9) {
1250 dist_normal_to_target = -vm_vec_dotprod(&vec_to_target, &aip->big_attack_surface_normal);
1251 } else {
1252 dist_normal_to_target = 0.2f * vm_vec_mag_quick(&vec_to_target);
1253 }
1254
1255 dist_normal_to_target = MAX(0.2f*dist_to_target, dist_normal_to_target);
1256 time_to_target = dist_normal_to_target / Pl_objp->phys_info.speed;
1257
1258 // add distance penalty for going too fast
1259 float speed_to_dist_penalty = MAX(0.0f, (Pl_objp->phys_info.speed-50));
1260
1261 //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))
1262
1263 // Inside 2 sec retreat, setting goal point to box point + 300m
1264 // If collision, use std collision resolution.
1265 if ( !(aip->ai_flags & AIF_KAMIKAZE) && ((aip->ai_flags & AIF_TARGET_COLLISION) || (time_to_target < STRAFE_RETREAT_COLLIDE_TIME) || (dist_normal_to_target < STRAFE_RETREAT_COLLIDE_DIST + speed_to_dist_penalty)) ) {
1266 if (aip->ai_flags & AIF_TARGET_COLLISION) {
1267 // use standard collision resolution
1268 aip->ai_flags &= ~AIF_TARGET_COLLISION;
1269 big_ship_collide_recover_start(Pl_objp, En_objp, &Pl_objp->pos, NULL);
1270 } else {
1271 // too close for comfort so fly to box point + 300
1272 aip->submode = AIS_STRAFE_RETREAT1;
1273 aip->submode_start_time = Missiontime;
1274
1275 float box_dist;
1276 int is_inside;
1277 vec3d goal_point;
1278 box_dist = get_world_closest_box_point_with_delta(&goal_point, En_objp, &Pl_objp->pos, &is_inside, STRAFE_RETREAT_BOX_DIST);
1279
1280 // set goal point
1281 aip->goal_point = goal_point;
1282
1283 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
1284 afterburners_start(Pl_objp);
1285 aip->afterburner_stop_time = Missiontime + 3*F1_0;
1286 }
1287 }
1288
1289 return 1;
1290 } else {
1291 return 0;
1292 }
1293 }
1294
1295 // attack directly to the turret and fire weapons
ai_big_strafe_attack()1296 void ai_big_strafe_attack()
1297 {
1298 ai_info *aip;
1299 vec3d target_pos;
1300 vec3d rand_vec;
1301 float target_dist, target_dot, accel, t;
1302
1303 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1304
1305 if ( ai_big_maybe_follow_subsys_path(0) ) {
1306 return;
1307 }
1308
1309 ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
1310 if ( ai_big_strafe_maybe_retreat(target_dist, &target_pos) )
1311 return;
1312
1313 if (aip->ai_flags & AIF_KAMIKAZE) {
1314 if (target_dist < 1200.0f) {
1315 ai_turn_towards_vector(&target_pos, Pl_objp, flFrametime, Ship_info[Ships[Pl_objp->instance].ship_info_index].srotation_time, NULL, NULL, 0.0f, 0);
1316 accelerate_ship(aip, 1.0f);
1317 if ((target_dist < 400.0f) && ai_maybe_fire_afterburner(Pl_objp, aip)) {
1318 afterburners_start(Pl_objp);
1319 aip->afterburner_stop_time = Missiontime + 3*F1_0;
1320 }
1321 return;
1322 }
1323 }
1324
1325 if (!(aip->ai_flags & AIF_SEEK_LOCK) || (aip->aspect_locked_time < 2.0f)) {
1326 t = ai_endangered_by_weapon(aip);
1327 if ( t > 0.0f && t < 1.5f ) {
1328 // set up goal_point for avoid path to turn towards
1329 aip->goal_point = Pl_objp->pos;
1330 switch(rand()%4) {
1331 case 0:
1332 vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, 2.0f);
1333 break;
1334 case 1:
1335 vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, -2.0f);
1336 break;
1337 case 2:
1338 vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec, 2.0f);
1339 break;
1340 case 3:
1341 vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec, -2.0f);
1342 break;
1343 } // end switch
1344
1345 vm_vec_scale(&rand_vec, 1000.0f);
1346 vm_vec_add2(&aip->goal_point, &rand_vec);
1347
1348 aip->submode = AIS_STRAFE_AVOID;
1349 aip->submode_start_time = Missiontime;
1350
1351 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
1352 afterburners_start(Pl_objp);
1353 aip->afterburner_stop_time = Missiontime + fl2f(0.5f);
1354 }
1355 }
1356 }
1357
1358 // maybe fire weapons at target
1359 ai_big_maybe_fire_weapons(target_dist, target_dot, &Pl_objp->pos, &En_objp->pos, &En_objp->phys_info.vel);
1360 turn_towards_point(Pl_objp, &target_pos, NULL, 0.0f);
1361
1362 // Slow down if we've not been hit for a while
1363 fix last_hit = Missiontime - aip->last_hit_time;
1364 if ( target_dist > 1200 || last_hit < F1_0*6) {
1365 accel = 1.0f;
1366 } else {
1367 float attack_time;
1368 attack_time = f2fl(Missiontime - aip->submode_start_time);
1369 if ( attack_time > 15 ) {
1370 accel = 0.2f;
1371 } else if ( attack_time > 10 ) {
1372 accel = 0.4f;
1373 } else if ( attack_time > 8 ) {
1374 accel = 0.6f;
1375 } else if ( attack_time > 5 ) {
1376 accel = 0.8f;
1377 } else {
1378 accel = 1.0f;
1379 }
1380 }
1381
1382 accel = 1.0f;
1383 accelerate_ship(aip, accel);
1384
1385 // if haven't been hit in quite a while, leave strafe mode
1386 fix long_enough;
1387 long_enough = F1_0 * STRAFE_MAX_UNHIT_TIME;
1388 if ( (last_hit > long_enough) && ( (Missiontime - aip->submode_parm0) > long_enough) ) {
1389 ai_big_switch_to_chase_mode(aip);
1390 }
1391 }
1392
1393 // SUSHI: Strafe attack using glide
1394 //submode_parm1 encodes the stage we're in.
1395 //0: Choosing goal point to fly towards
1396 //1: Accelerating towards the goal point.
1397 //2: Raining down fire and brimstone on the target.
1398 //3: Dummy stage used when resetting or exiting the mode
ai_big_strafe_glide_attack()1399 void ai_big_strafe_glide_attack()
1400 {
1401 ai_info *aip;
1402 vec3d target_pos, vec_to_goal, predicted_enemy_pos, norm_vel_vec;
1403 float target_dist, target_dot, target_ship_dist, dot_to_goal, flight_dot_to_enemy;
1404 object *target_objp;
1405
1406 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1407 target_objp = &Objects[aip->target_objnum];
1408
1409 vm_vec_sub(&vec_to_goal, &aip->goal_point, &Pl_objp->pos);
1410 vm_vec_normalize(&vec_to_goal);
1411
1412 if (vm_vec_mag(&Pl_objp->phys_info.vel) > 0.0f)
1413 vm_vec_copy_normalize(&norm_vel_vec, &Pl_objp->phys_info.vel);
1414 else
1415 vm_vec_copy_normalize(&norm_vel_vec, &Pl_objp->orient.vec.fvec);
1416
1417 dot_to_goal = vm_vec_dot(&vec_to_goal, &norm_vel_vec); //Angle between flight path and goal point
1418 flight_dot_to_enemy = vm_vec_dot(&target_objp->pos, &norm_vel_vec); //Angle between flight path and target ship
1419
1420 //If we're following a path, then we shouldn't be doing glide strafe
1421 if ( ai_big_maybe_follow_subsys_path(0) ) {
1422 return;
1423 }
1424
1425 //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.
1426 ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
1427 target_ship_dist = vm_vec_dist(&target_objp->pos, &Pl_objp->pos);
1428
1429 //If we haven't chosen the goal point yet, do so now
1430 if (aip->submode_parm1 == 0) {
1431 //Pick goal point. This should be a random point that passes through the circle whose normal is the vector between attacker and target
1432 vec3d targetToAttacker;
1433 vec3d tangentPoint;
1434 vm_vec_sub(&targetToAttacker, &Pl_objp->pos, &target_objp->pos);
1435 matrix orient;
1436 vm_vector_2_matrix(&orient, &targetToAttacker, NULL, NULL);
1437 vm_vec_random_in_circle(&tangentPoint, &target_objp->pos, &orient, target_objp->radius + GLIDE_STRAFE_DISTANCE, 1);
1438 //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
1439 vm_vec_sub2(&tangentPoint, &Pl_objp->pos);
1440 vm_vec_scale(&tangentPoint, 1000.0f);
1441 vm_vec_add(&aip->goal_point, &tangentPoint, &Pl_objp->pos);
1442 aip->submode_parm1 = 1;
1443 }
1444 if (aip->submode_parm1 == 1) {
1445 //Stay in acceleration stage?
1446 //Accelerate towards goal point until:
1447 // Mostly up to speed AND
1448 // Moving in the correct direction
1449 if ((Pl_objp->phys_info.fspeed > (Pl_objp->phys_info.max_vel.xyz.z * 0.85f)) && (dot_to_goal > 0.99f))
1450 aip->submode_parm1 = 2;
1451 }
1452 if (aip->submode_parm1 == 2) {
1453 //Should we stay in the fire stage?
1454 //Keep going until we are too far away.
1455 //If we are still on approach but too far away, this will still trigger. This will allow us to reposition the target
1456 //point and allow for a "jinking" effect.
1457 if (target_ship_dist > (STRAFE_RETREAT_BOX_DIST + target_objp->radius) &&
1458 Missiontime - aip->submode_start_time > i2f(GLIDE_STRAFE_MIN_TIME)) {
1459 //This checks whether we are moving toward the target or away from it. If moving towards, we reset the stage so that we
1460 //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
1461 //switch back to conventional strafe).
1462 if (flight_dot_to_enemy > 0.0f) {
1463 aip->submode_parm1 = 0;
1464 }
1465 else {
1466 aip->submode_parm1 = 3;
1467 aip->submode = AIS_STRAFE_POSITION;
1468 }
1469 aip->submode_start_time = Missiontime;
1470 }
1471 }
1472 //Time limit: if we've taken too long, reset
1473 if (Missiontime - aip->submode_start_time > i2f(GLIDE_STRAFE_MAX_TIME)) {
1474 aip->submode_parm1 = 3;
1475 aip->submode = AIS_STRAFE_POSITION;
1476 aip->submode_start_time = Missiontime;
1477 }
1478
1479 //Acceleration stage
1480 if (aip->submode_parm1 == 1) {
1481 accelerate_ship(aip, 1.0f);
1482 //Use afterburners if we have them and are pointed the right way
1483 if (dot_to_goal > 0.99f) {
1484 afterburners_start(Pl_objp);
1485 aip->afterburner_stop_time = Missiontime + 3*F1_0;
1486 }
1487
1488 turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
1489 }
1490 //Fire stage
1491 if (aip->submode_parm1 == 2) {
1492 Pl_objp->phys_info.flags |= PF_GLIDING;
1493 accelerate_ship(aip, 0.0f);
1494 afterburners_stop(Pl_objp);
1495
1496 //Compensate for motion of ship and target
1497 set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &En_objp->pos, &En_objp->phys_info.vel, aip);
1498 vm_vec_add2(&target_pos, &predicted_enemy_pos);
1499 vm_vec_sub2(&target_pos, &En_objp->pos);
1500
1501 turn_towards_point(Pl_objp, &target_pos, NULL, 0.0f);
1502 ai_big_maybe_fire_weapons(target_dist, target_dot, &Pl_objp->pos, &En_objp->pos, &En_objp->phys_info.vel);
1503 }
1504
1505 // if haven't been hit in quite a while, leave strafe mode
1506 // (same as ai_big_strafe_attack)
1507 fix long_enough = F1_0 * STRAFE_MAX_UNHIT_TIME;
1508 if ( (Missiontime - aip->last_hit_time > long_enough) && ( (Missiontime - aip->submode_parm0) > long_enough) ) {
1509 ai_big_switch_to_chase_mode(aip);
1510 }
1511 }
1512
1513 // pick a new attack point when entering this state, and keep using it
ai_big_strafe_avoid()1514 void ai_big_strafe_avoid()
1515 {
1516 ai_info *aip;
1517 vec3d target_pos;
1518 float target_dist, target_dot;
1519 fix mode_time;
1520
1521 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1522
1523 mode_time = Missiontime - aip->submode_start_time;
1524
1525 ai_big_attack_get_data(&target_pos, &target_dist, &target_dot);
1526 if ( ai_big_strafe_maybe_retreat(target_dist, &target_pos) )
1527 return;
1528
1529 if ( mode_time > fl2f(0.5)) {
1530 ai_big_strafe_position();
1531 }
1532
1533 turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
1534 accelerate_ship(aip, 1.0f);
1535 }
1536
1537 // move towards aip->goal_point in an evasive manner
ai_big_strafe_retreat1()1538 void ai_big_strafe_retreat1()
1539 {
1540 float dist;
1541 ai_info *aip;
1542 vec3d rand_vec;
1543
1544 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1545
1546 dist = vm_vec_dist_quick(&Pl_objp->pos, &aip->goal_point);
1547 if ( dist < 70 ) {
1548 aip->submode = AIS_STRAFE_POSITION;
1549 aip->submode_start_time = Missiontime;
1550 // nprintf(("Alan","Ship %s entering AIS_STRAFE_POSITION\n", Ships[aip->shipnum].ship_name));
1551 return;
1552 }
1553
1554 if (Missiontime - aip->submode_start_time > fl2f(1.50f)) {
1555 // set up goal_point for avoid path to turn towards
1556 aip->prev_goal_point = Pl_objp->pos;
1557 switch(rand()%4) {
1558 case 0:
1559 vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec);
1560 break;
1561 case 1:
1562 vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.uvec, -1.0f);
1563 break;
1564 case 2:
1565 vm_vec_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec);
1566 break;
1567 case 3:
1568 vm_vec_scale_add(&rand_vec, &Pl_objp->orient.vec.fvec, &Pl_objp->orient.vec.rvec, -1.0f);
1569 break;
1570 } // end switch
1571
1572 //vm_vec_scale(&rand_vec, 200.0f);
1573 //vm_vec_add2(&aip->prev_goal_point, &rand_vec);
1574 vm_vec_scale_add(&aip->prev_goal_point, &aip->goal_point, &rand_vec, 200.0f);
1575
1576 aip->submode = AIS_STRAFE_RETREAT2;
1577 aip->submode_start_time = Missiontime;
1578 }
1579
1580 turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
1581 accelerate_ship(aip, 1.0f);
1582 }
1583
ai_big_strafe_retreat2()1584 void ai_big_strafe_retreat2()
1585 {
1586 float dist;
1587 ai_info *aip;
1588
1589 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1590
1591 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON ) {
1592 if (Missiontime > aip->afterburner_stop_time) {
1593 //nprintf(("AI", "Frame %i, turning off afterburner.\n", AI_FrameCount));
1594 afterburners_stop(Pl_objp);
1595 }
1596 }
1597
1598 dist = vm_vec_dist_quick(&Pl_objp->pos, &aip->goal_point);
1599 if ( dist < 70 ) {
1600 aip->submode = AIS_STRAFE_POSITION;
1601 aip->submode_start_time = Missiontime;
1602 // nprintf(("Alan","Ship %s entering AIS_STRAFE_POSITION\n", Ships[aip->shipnum].ship_name));
1603 return;
1604 }
1605
1606 if (Missiontime - aip->submode_start_time > fl2f(0.70f)) {
1607 aip->submode = AIS_STRAFE_RETREAT1;
1608 aip->submode_start_time = Missiontime;
1609
1610 if ( (Missiontime - aip->last_hit_time) < F1_0*5 ) {
1611 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
1612 afterburners_start(Pl_objp);
1613 aip->afterburner_stop_time = Missiontime + F1_0;
1614 }
1615 }
1616 }
1617
1618 turn_towards_point(Pl_objp, &aip->prev_goal_point, NULL, 0.0f);
1619 accelerate_ship(aip, 1.0f);
1620 }
1621
1622 // reposition self to begin another strafing run
ai_big_strafe_position()1623 void ai_big_strafe_position()
1624 {
1625 ship_info *sip;
1626 ai_info *aip;
1627 sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
1628 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1629
1630 //Maybe use AIS_STRAFE_GLIDE_ATTACK
1631 if ((sip->can_glide == true) && !(aip->ai_flags & AIF_KAMIKAZE) && (frand() < aip->ai_glide_strafe_percent)) {
1632 aip->submode = AIS_STRAFE_GLIDE_ATTACK;
1633 aip->submode_parm1 = 0;
1634 } else {
1635 aip->submode = AIS_STRAFE_ATTACK;
1636 }
1637 aip->submode_start_time = Missiontime;
1638 }
1639
1640 // --------------------------------------------------------------------------
1641 // #define AIS_STRAFE_ATTACK 201 // fly towards target and attack
1642 // #define AIS_STRAFE_AVOID 202 // fly evasive vector to avoid incoming fire
1643 // #define AIS_STRAFE_RETREAT1 203 // fly away from attack point (directly)
1644 // #define AIS_STRAFE_RETREAT1 204 // fly away from attack point (on an avoid vector)
1645 // #define AIS_STRAFE_POSITION 205 // re-position to resume strafing attack
1646 //
ai_big_strafe()1647 void ai_big_strafe()
1648 {
1649 ai_info *aip;
1650
1651 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
1652
1653 Assert(aip->mode == AIM_STRAFE);
1654
1655 /*
1656 if ( aip->goal_objnum != aip->target_objnum ) {
1657 Int3(); // what is going on here? - Get Alan
1658 aip->mode = AIM_NONE;
1659 return;
1660 }
1661 */
1662 // check if target is still a big ship... if not enter chase mode
1663 if ( !(Ship_info[Ships[En_objp->instance].ship_info_index].flags & (SIF_BIG_SHIP|SIF_HUGE_SHIP)) ) {
1664 ai_big_switch_to_chase_mode(aip);
1665 return;
1666 }
1667
1668 switch (aip->submode) {
1669 case AIS_STRAFE_ATTACK:
1670 ai_big_strafe_attack();
1671 break;
1672 case AIS_STRAFE_AVOID:
1673 ai_big_strafe_avoid();
1674 break;
1675 case AIS_STRAFE_RETREAT1:
1676 ai_big_strafe_retreat1();
1677 break;
1678 case AIS_STRAFE_RETREAT2:
1679 ai_big_strafe_retreat2();
1680 break;
1681 case AIS_STRAFE_POSITION:
1682 ai_big_strafe_position();
1683 break;
1684 case AIS_STRAFE_GLIDE_ATTACK:
1685 ai_big_strafe_glide_attack();
1686 break;
1687 default:
1688
1689 Int3(); // Illegal submode for AIM_STRAFE
1690 break;
1691 }
1692
1693 //Maybe apply random sidethrust, depending on the current submode
1694 //The following are valid targets for random sidethrust (circle strafe uses it too, but that is handled separately)
1695 if (aip->submode == AIS_STRAFE_ATTACK ||
1696 aip->submode == AIS_STRAFE_AVOID ||
1697 aip->submode == AIS_STRAFE_RETREAT1 ||
1698 aip->submode == AIS_STRAFE_RETREAT2 ||
1699 aip->submode == AIS_STRAFE_POSITION)
1700 {
1701 //Re-roll for random sidethrust every 2 seconds
1702 if (static_randf((Missiontime + static_rand(aip->shipnum)) >> 17) < aip->ai_random_sidethrust_percent) {
1703 do_random_sidethrust(aip, &Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index]);
1704 }
1705 }
1706 }
1707
1708 // See if Pl_objp should enter strafe mode (This is called from maybe_evade_dumbfire_weapon(), and the
1709 // weapon_objnum is for a weapon that is about to collide with Pl_objp
1710 //
1711 // Check if weapon_objnum was fired by Pl_objp's target, and whether Pl_objp's target is a big ship, if
1712 // so, enter AIM_STRAFE
ai_big_maybe_enter_strafe_mode(object * pl_objp,int weapon_objnum,int consider_target_only)1713 int ai_big_maybe_enter_strafe_mode(object *pl_objp, int weapon_objnum, int consider_target_only)
1714 {
1715 ai_info *aip;
1716 ship_info *sip;
1717 object *weapon_objp, *parent_objp;
1718
1719 aip = &Ai_info[Ships[pl_objp->instance].ai_index];
1720 Assert(aip->mode != AIM_STRAFE); // can't happen
1721
1722 // if Pl_objp has no target, then we can't enter strafe mode
1723 if ( aip->target_objnum < 0 ) {
1724 return 0;
1725 }
1726
1727 // if target is not a ship, stafe mode is not possible
1728 if ( Objects[aip->target_objnum].type != OBJ_SHIP ) {
1729 return 0;
1730 }
1731
1732 sip = &Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index];
1733
1734 // if Pl_objp's target is not a big/capital ship, then cannot enter strafe mode
1735 // AL 12-31-97: Even though transports are considered big ships, don't enter strafe mode on them
1736 if ( !(sip->flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) || (sip->flags & SIF_TRANSPORT) ) {
1737 return 0;
1738 }
1739
1740 // If Pl_objp not a fighter or bomber, don't enter strafe mode. -- MK, 11/11/97.
1741 if ( !(Ship_info[Ships[pl_objp->instance].ship_info_index].flags & (SIF_FIGHTER | SIF_BOMBER)) ) {
1742 return 0;
1743 }
1744
1745 Assert(weapon_objnum >= 0 && weapon_objnum < MAX_OBJECTS);
1746 weapon_objp = &Objects[weapon_objnum];
1747 Assert(weapon_objp->type == OBJ_WEAPON);
1748
1749 Assert(weapon_objp->parent >= 0 && weapon_objp->parent < MAX_OBJECTS);
1750 parent_objp = &Objects[weapon_objp->parent];
1751 if ( (parent_objp->signature != weapon_objp->parent_sig) || (parent_objp->type != OBJ_SHIP) ) {
1752 return 0;
1753 }
1754
1755 // Maybe the ship which fired the weapon isn't the current target
1756 if ( OBJ_INDEX(parent_objp) != aip->target_objnum ) {
1757
1758 //JAS IMPOSSIBLE if (1) { // consider_target_only ) {
1759 //JAS IMPOSSIBLE return 0;
1760 //JAS IMPOSSIBLE } else {
1761 // switch targets
1762 sip = &Ship_info[Ships[parent_objp->instance].ship_info_index];
1763 if ( !(sip->flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) || (sip->flags & SIF_TRANSPORT) ) {
1764 return 0;
1765 }
1766 set_target_objnum(aip, OBJ_INDEX(parent_objp));
1767 //JAS IMPOSSIBLE }
1768 }
1769
1770 ai_big_strafe_maybe_attack_turret(pl_objp, weapon_objp);
1771
1772 // if we've got this far, the weapon must have come from the player's target, and it is a
1773 // big/capital ship... so enter strafe mode
1774 aip->previous_mode = aip->mode;
1775 aip->mode = AIM_STRAFE;
1776 aip->submode_parm0 = Missiontime; // use parm0 as time strafe mode entered (i.e. MODE start time)
1777 aip->submode = AIS_STRAFE_AVOID;
1778 aip->submode_start_time = Missiontime;
1779 // nprintf(("Alan","%s Accepted strafe mode\n", Ships[pl_objp->instance].ship_name));
1780
1781 return 1;
1782 }
1783
1784 // Consider attacking a turret, if a turret actually fired the weapon
1785 // input: ship_objp => ship that will attack the turret
1786 // weapon_objp =>
ai_big_strafe_maybe_attack_turret(object * ship_objp,object * weapon_objp)1787 void ai_big_strafe_maybe_attack_turret(object *ship_objp, object *weapon_objp)
1788 {
1789 ai_info *aip;
1790 object *parent_objp;
1791
1792 Assert(ship_objp->type == OBJ_SHIP);
1793 aip = &Ai_info[Ships[ship_objp->instance].ai_index];
1794
1795 // Make decision to attack turret based on AI class. The better AI ships will realize that
1796 // it is better to take out the turrets first on a big ship.
1797 if ( (frand()*100) > (aip->ai_courage-15) )
1798 return;
1799
1800 // If ship is already attacking a subsystem, don't switch
1801 if ( aip->targeted_subsys != NULL ) {
1802 return;
1803 }
1804
1805 // If the weapon is not from a turret, return
1806 if ( Weapons[weapon_objp->instance].turret_subsys == NULL ) {
1807 return;
1808 }
1809
1810 Assert(weapon_objp->parent >= 0 && weapon_objp->parent < MAX_OBJECTS);
1811 parent_objp = &Objects[weapon_objp->parent];
1812
1813 // UnknownPlayer : Decide whether or not this weapon was a beam, in which case it might be a good
1814 // idea to go after it even if its not on the current target. This is randomized so
1815 // the ai will not always go after different ships firing beams at them.
1816 // Approx 1/4 chance we'll go after the other ship's beam.
1817
1818 bool attack_turret_on_different_ship = (aip->ai_profile_flags & AIPF_BIG_SHIPS_CAN_ATTACK_BEAM_TURRETS_ON_UNTARGETED_SHIPS)
1819 && (Weapon_info[Weapons[weapon_objp->instance].weapon_info_index].wi_flags & WIF_BEAM) && (frand()*100 < 25.0f);
1820
1821 // unless we're making an exception, we should only attack a turret if it sits on the current target
1822 if ( !attack_turret_on_different_ship )
1823 {
1824 if ( (parent_objp->signature != weapon_objp->parent_sig) || (parent_objp->type != OBJ_SHIP) ) {
1825 return;
1826 }
1827
1828 if ( aip->target_objnum != OBJ_INDEX(parent_objp) ) {
1829 return;
1830 }
1831 }
1832
1833 // attack the turret
1834 set_targeted_subsys(aip, Weapons[weapon_objp->instance].turret_subsys, OBJ_INDEX(parent_objp));
1835 }
1836