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 /**
13 * @file
14 * Contains the actual AI code that does interesting stuff to objects.
15 *
16 * The code in Ai.cpp is just for bookkeeping, allocating AI slots and linking them to ships.
17 */
18
19
20 #include "ai/ai.h"
21 #include "ai/ai_profiles.h"
22 #include "ai/aibig.h"
23 #include "ai/aigoals.h"
24 #include "ai/aiinternal.h"
25 #include "asteroid/asteroid.h"
26 #include "autopilot/autopilot.h"
27 #include "cmeasure/cmeasure.h"
28 #include "debris/debris.h"
29 #include "debugconsole/console.h"
30 #include "freespace.h"
31 #include "gamesequence/gamesequence.h"
32 #include "gamesnd/gamesnd.h"
33 #include "globalincs/linklist.h"
34 #include "hud/hud.h"
35 #include "hud/hudets.h"
36 #include "hud/hudlock.h"
37 #include "iff_defs/iff_defs.h"
38 #include "io/joy_ff.h"
39 #include "io/timer.h"
40 #include "localization/localize.h"
41 #include "math/fvi.h"
42 #include "math/staticrand.h"
43 #include "mission/missiongoals.h"
44 #include "mission/missionlog.h"
45 #include "mission/missionmessage.h"
46 #include "mission/missionparse.h"
47 #include "mission/missiontraining.h"
48 #include "model/model.h"
49 #include "network/multi.h"
50 #include "network/multimsgs.h"
51 #include "network/multiutil.h"
52 #include "object/deadobjectdock.h"
53 #include "object/objcollide.h"
54 #include "object/object.h"
55 #include "object/objectdock.h"
56 #include "object/objectshield.h"
57 #include "object/waypoint.h"
58 #include "parse/parselo.h"
59 #include "physics/physics.h"
60 #include "playerman/player.h"
61 #include "render/3d.h"
62 #include "scripting/api/objs/waypoint.h"
63 #include "scripting/api/objs/wing.h"
64 #include "scripting/scripting.h"
65 #include "ship/afterburner.h"
66 #include "ship/awacs.h"
67 #include "ship/ship.h"
68 #include "ship/shipfx.h"
69 #include "ship/shiphit.h"
70 #include "ship/subsysdamage.h"
71 #include "utils/Random.h"
72 #include "weapon/beam.h"
73 #include "weapon/flak.h"
74 #include "weapon/swarm.h"
75 #include "weapon/weapon.h"
76 #include <map>
77 #include <climits>
78
79
80 #ifdef _MSC_VER
81 #pragma optimize("", off)
82 #pragma auto_inline(off)
83 #endif
84
85 #define UNINITIALIZED_VALUE -99999.9f
86
87 #define INSTRUCTOR_SHIP_NAME NOX("instructor")
88
89 #define AICODE_SMALL_MAGNITUDE 0.001f // cosider a vector NULL if mag is less than this
90
91 #define NEXT_REARM_TIMESTAMP (60*1000) // Ships will re-request rearm, typically, after this long.
92
93 #define CIRCLE_STRAFE_MAX_DIST 300.0f //Maximum distance for circle strafe behavior.
94
95 // AIM_CHASE submode defines
96 // SM_STEALTH_FIND
97 #define SM_SF_AHEAD 0
98 #define SM_SF_BEHIND 1
99 #define SM_SF_BAIL 2
100
101 // SM_STEALTH_SWEEP
102 #define SM_SS_SET_GOAL -1
103 #define SM_SS_BOX0 0
104 #define SM_SS_LR 1
105 #define SM_SS_UL 2
106 #define SM_SS_BOX1 3
107 #define SM_SS_UR 4
108 #define SM_SS_LL 5
109 #define SM_SS_BOX2 6
110 #define SM_SS_DONE 7
111
112 //XSTR:OFF
113
114 const char *Mode_text[MAX_AI_BEHAVIORS] = {
115 "CHASE",
116 "EVADE",
117 "GET_BEHIND",
118 "CHASE_LONG",
119 "SQUIGGLE",
120 "GUARD",
121 "AVOID",
122 "WAYPOINTS",
123 "DOCK",
124 "NONE",
125 "BIGSHIP",
126 "PATH",
127 "BE_REARMED",
128 "SAFETY",
129 "EV_WEAPON",
130 "STRAFE",
131 "PLAY_DEAD",
132 "BAY_EMERGE",
133 "BAY_DEPART",
134 "SENTRYGUN",
135 "WARP_OUT",
136 };
137
138 // Submode text is only valid for CHASE mode.
139 const char *Submode_text[] = {
140 "undefined",
141 "CONT_TURN",
142 "ATTACK ",
143 "E_SQUIG ",
144 "E_BRAKE ",
145 "EVADE ",
146 "SUP_ATTAK",
147 "AVOID ",
148 "BEHIND ",
149 "GET_AWAY ",
150 "E_WEAPON ",
151 "FLY_AWAY ",
152 "ATK_4EVER",
153 "STLTH_FND",
154 "STLTH_SWP",
155 "BIG_APPR",
156 "BIG_CIRC",
157 "BIG_PARL"
158 "GLIDE_ATK"
159 "CIR_STRAFE"
160 };
161
162 const char *Strafe_submode_text[5] = {
163 "ATTACK",
164 "AVOID",
165 "RETREAT1",
166 "RETREAT2",
167 "POSITION"
168 };
169 //XSTR:ON
170
171 // few forward decs i needed - kazan
172 object * get_wing_leader(int wingnum);
173 int get_wing_index(object *objp, int wingnum);
174
175 control_info AI_ci;
176
177 object *Pl_objp;
178 object *En_objp;
179
180 #define REARM_SOUND_DELAY (3*F1_0) // Amount of time to delay rearm/repair after mode start
181 #define REARM_BREAKOFF_DELAY (3*F1_0) // Amount of time to wait after fully rearmed to breakoff.
182
183 #define MIN_DIST_TO_WAYPOINT_GOAL 5.0f
184 #define MAX_GUARD_DIST 250.0f
185 #define BIG_GUARD_RADIUS 500.0f
186
187 #define MAX_EVADE_TIME (15 * 1000) // Max time to evade a weapon.
188
189 // defines for repair ship stuff.
190 #define MAX_REPAIR_SPEED 25.0f
191 #define MAX_UNDOCK_ABORT_SPEED 2.0f
192
193 // defines for EMP effect stuff
194 #define MAX_EMP_INACCURACY 50.0f
195
196 // defines for stealth
197 #define MAX_STEALTH_INACCURACY 50.0f // at max view dist
198 #define STEALTH_MAX_VIEW_DIST 400 // dist at which 1) stealth no longer visible 2) firing inaccuracy is greatest
199 #define STEALTH_VIEW_CONE_DOT 0.707 // (half angle of 45 degrees)
200
201 ai_class *Ai_classes = NULL;
202 int Ai_firing_enabled = 1;
203 int Num_ai_classes;
204 int Num_alloced_ai_classes;
205
206 int AI_FrameCount = 0;
207 int AI_watch_object = 0; // Debugging, object to spew debug info for.
208 int Mission_all_attack = 0; // !0 means all teams attack all teams.
209
210 // Constant for flag, Name of flag
211 ai_flag_name Ai_flag_names[] = {
212 {AI::AI_Flags::No_dynamic, "no-dynamic", },
213 {AI::AI_Flags::Free_afterburner_use, "free-afterburner-use", },
214 };
215
Skill_level_names(int level,int translate)216 const char *Skill_level_names(int level, int translate)
217 {
218 const char *str = NULL;
219
220 #if NUM_SKILL_LEVELS != 5
221 #error Number of skill levels is wrong!
222 #endif
223
224 if(translate){
225 switch( level ) {
226 case 0:
227 str = XSTR("Very Easy", 469);
228 break;
229 case 1:
230 str = XSTR("Easy", 470);
231 break;
232 case 2:
233 str = XSTR("Medium", 471);
234 break;
235 case 3:
236 str = XSTR("Hard", 472);
237 break;
238 case 4:
239 str = XSTR("Insane", 473);
240 break;
241 default:
242 UNREACHABLE("Unhandled difficulty level of '%d' was passed to Skill_level_names().", level);
243 }
244 } else {
245 switch( level ) {
246 case 0:
247 str = NOX("Very Easy");
248 break;
249 case 1:
250 str = NOX("Easy");
251 break;
252 case 2:
253 str = NOX("Medium");
254 break;
255 case 3:
256 str = NOX("Hard");
257 break;
258 case 4:
259 str = NOX("Insane");
260 break;
261 default:
262 UNREACHABLE("An invalid difficulty level of '%d' was passed to Skill_level_names().", level);
263 }
264 }
265
266 return str;
267 }
268
269 #define DELAY_TARGET_TIME (12*1000) // time in milliseconds until a ship can target a new enemy after an order.
270
271 pnode Path_points[MAX_PATH_POINTS];
272 pnode *Ppfp; // Free pointer in path points.
273
274 float AI_frametime;
275
276 char** Ai_class_names = NULL;
277
278 typedef struct {
279 int team;
280 int weapon_index;
281 int max_fire_count;
282 const char *shipname;
283 } huge_fire_info;
284
285 SCP_vector<huge_fire_info> Ai_huge_fire_info;
286
287 int Ai_last_arrive_path; // index of ship_bay path used by last arrival from a fighter bay
288
289 // forward declarations
290 int ai_return_path_num_from_dockbay(object *dockee_objp, int dockbay_index);
291 void create_model_exit_path(object *pl_objp, object *mobjp, int path_num, int count=1);
292 void copy_xlate_model_path_points(object *objp, model_path *mp, int dir, int count, int path_num, pnode *pnp, int randomize_pnt=-1);
293 void ai_cleanup_rearm_mode(object *objp);
294 void ai_cleanup_dock_mode_subjective(object *objp);
295 void ai_cleanup_dock_mode_objective(object *objp);
296
297 // The object that is declared to be the leader of the group formation for
298 // the "autopilot"
299 object *Autopilot_flight_leader = NULL;
300
301 /**
302 * Sets the timestamp used to tell is it is a good time for this team to rearm.
303 * Once the timestamp is no longer valid, then rearming is not a "good time"
304 *
305 * @param team Team (friendly, hostile, neutral)
306 * @param time Time to rearm
307 */
ai_set_rearm_status(int team,int time)308 void ai_set_rearm_status(int team, int time)
309 {
310 Assert( time >= 0 );
311
312 Iff_info[team].ai_rearm_timestamp = timestamp(time * 1000);
313 }
314
315 /**
316 * "safe" is currently defined by the mission designer using the good/bad time to rearm sexpressions. This status is currently team based.
317 *
318 * @return true(1) or false(0) if it is "safe" for the given object to rearm.
319 * @todo This function could be easily expended to further the definition of "safe"
320 */
ai_good_time_to_rearm(object * objp)321 int ai_good_time_to_rearm(object *objp)
322 {
323 int team;
324
325 Assert(objp->type == OBJ_SHIP);
326 team = Ships[objp->instance].team;
327
328 return timestamp_valid(Iff_info[team].ai_rearm_timestamp);
329 }
330
331 /**
332 * Entry point from sexpression code to set internal data for use by AI code.
333 */
ai_good_secondary_time(int team,int weapon_index,int max_fire_count,const char * shipname)334 void ai_good_secondary_time( int team, int weapon_index, int max_fire_count, const char *shipname )
335 {
336 Assert(shipname != NULL);
337 int index;
338 huge_fire_info new_info;
339
340 new_info.weapon_index = weapon_index;
341 new_info.team = team;
342 new_info.max_fire_count = max_fire_count;
343
344 new_info.shipname = ai_get_goal_target_name( shipname, &index );
345
346 Ai_huge_fire_info.push_back(new_info);
347 }
348
349 /**
350 * Called internally to the AI code to tell whether or not weapon_num can be fired
351 * from firer_objp at target_objp. This function will resolve the team for the firer.
352 *
353 * @return -1 when conditions don't allow firer to fire weapon_num on target_objp
354 * @return >=0 when conditions allow firer to fire. Return value is max number of weapon_nums which can be fired on target_objp
355 */
is_preferred_weapon(int weapon_num,object * firer_objp,object * target_objp)356 int is_preferred_weapon(int weapon_num, object *firer_objp, object *target_objp)
357 {
358 int firer_team, target_signature;
359 ship *firer_ship;
360 SCP_vector<huge_fire_info>::iterator hfi;
361
362 Assert( firer_objp->type == OBJ_SHIP );
363 firer_ship = &Ships[firer_objp->instance];
364 firer_team = firer_ship->team;
365
366 // get target object's signature and try to find it in the list.
367 target_signature = target_objp->signature;
368 for (hfi = Ai_huge_fire_info.begin();hfi != Ai_huge_fire_info.end(); ++hfi ) {
369 int ship_index, signature;
370
371 if ( hfi->weapon_index == -1 )
372 continue;
373
374 ship_index = ship_name_lookup( hfi->shipname );
375 if ( ship_index == -1 )
376 continue;
377
378 signature = Objects[Ships[ship_index].objnum].signature;
379
380 // sigatures, weapon_index, and team must match
381 if ( (signature == target_signature) && (hfi->weapon_index == weapon_num) && (hfi->team == firer_team) )
382 break;
383 }
384
385 // return -1 if not found
386 if ( hfi == Ai_huge_fire_info.end() )
387 return -1;
388
389 // otherwise, we can return the max number of weapons we can fire against target_objps
390 return hfi->max_fire_count;
391 }
392
393 /**
394 * Clear out secondary firing information between levels
395 */
ai_init_secondary_info()396 void ai_init_secondary_info()
397 {
398 Ai_huge_fire_info.clear();
399 }
400
401 /**
402 * Garbage collect the ::Path_points buffer.
403 *
404 * Scans all objects, looking for used Path_points records.
405 * Compresses Path_points buffer, updating aip->path_start and aip->path_cur indices.
406 * Updates Ppfp to point to first free record.
407 * This function is fairly fast. Its worst-case running time is proportional to
408 * 3*MAX_PATH_POINTS + MAX_OBJECTS
409 *
410 * @todo Things to do to optimize this function:
411 * 1. if (t != 0) xlt++; can be replaced by xlt += t; assuming t can only be 0 or 1.
412 * 2. When pp_xlate is getting stuffed the first time, note highest index and use that
413 * instead of MAX_PATH_POINTS in following two for loops.
414 */
garbage_collect_path_points()415 void garbage_collect_path_points()
416 {
417 int i;
418 int pp_xlate[MAX_PATH_POINTS];
419 object *A;
420 ship_obj *so;
421
422 // Scan all objects and create Path_points xlate table.
423 for (i=0; i<MAX_PATH_POINTS; i++)
424 pp_xlate[i] = 0;
425
426 // in pp_xlate, mark all used Path_point records
427 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
428 A = &Objects[so->objnum];
429 ship *shipp = &Ships[A->instance];
430 if (shipp->ai_index != -1) {
431 ai_info *aip = &Ai_info[shipp->ai_index];
432
433 if ((aip->path_length > 0) && (aip->path_start > -1)) {
434
435 for (i=aip->path_start; i<aip->path_start + aip->path_length; i++) {
436 Assert(pp_xlate[i] == 0); // If this is not 0, then two paths use this point!
437 pp_xlate[i] = 1;
438 }
439 }
440 }
441 }
442
443 // Now, stuff xlate index in pp_xlate. This is the number to translate any path_start
444 // or path_cur index to.
445 int xlt = 0;
446 for (i=0; i<MAX_PATH_POINTS; i++) {
447 int t = pp_xlate[i];
448
449 pp_xlate[i] = xlt;
450 if (t != 0)
451 xlt++;
452 }
453
454 // Update global Path_points free pointer.
455 Ppfp = &Path_points[xlt];
456
457 // Now, using pp_xlate, fixup all aip->path_cur and aip->path_start indices
458 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
459 A = &Objects[so->objnum];
460 ship *shipp = &Ships[A->instance];
461 if (shipp->ai_index != -1) {
462 ai_info *aip = &Ai_info[shipp->ai_index];
463
464 if ((aip->path_length > 0) && (aip->path_start > -1)) {
465 Assert(aip->path_start < MAX_PATH_POINTS);
466 aip->path_start = pp_xlate[aip->path_start];
467
468 Assert((aip->path_cur >= 0) && (aip->path_cur < MAX_PATH_POINTS));
469 aip->path_cur = pp_xlate[aip->path_cur];
470 }
471 }
472 }
473
474 // Now, compress the buffer.
475 for (i=0; i<MAX_PATH_POINTS; i++)
476 if (i != pp_xlate[i])
477 Path_points[pp_xlate[i]] = Path_points[i];
478
479 }
480
481 /**
482 * Hash two values together, return result.
483 * Hash function: curval shifted right circular by one, newval xored in.
484 */
hash(unsigned int curval,int newval)485 int hash(unsigned int curval, int newval)
486 {
487 int addval = curval & 1;
488
489 curval >>= 1;
490 if (addval)
491 curval |= 0x80000000;
492 curval ^= newval;
493
494 return curval;
495 }
496
497 /**
498 * Hash some information in an object together.
499 * On 2/20/97, the information is position and orientation.
500 */
create_object_hash(object * objp)501 int create_object_hash(object *objp)
502 {
503 int *ip;
504 unsigned int hashval = 0;
505 int i;
506
507 ip = (int *) &objp->orient;
508
509 for (i=0; i<9; i++) {
510 hashval = hash(hashval, *ip);
511 ip++;
512 }
513
514 ip = (int *) &objp->pos;
515
516 for (i=0; i<3; i++) {
517 hashval = hash(hashval, *ip);
518 ip++;
519 }
520
521 return hashval;
522 }
523
free_ai_stuff()524 void free_ai_stuff()
525 {
526 if(Ai_classes != NULL)
527 vm_free(Ai_classes);
528
529 if(Ai_class_names != NULL)
530 vm_free(Ai_class_names);
531 }
532
533 /**
534 * Initialize an AI class's nonrequired values to defaults
535 * Boolean overrides are unset, others initialized to FLT_MIN or INT_MIN (used as the "not set" state)
536 */
init_ai_class(ai_class * aicp)537 void init_ai_class(ai_class *aicp)
538 {
539 int i;
540 for (i = 0; i < NUM_SKILL_LEVELS; i++)
541 {
542 aicp->ai_cmeasure_fire_chance[i] = FLT_MIN;
543 aicp->ai_in_range_time[i] = FLT_MIN;
544 aicp->ai_link_ammo_levels_maybe[i] = FLT_MIN;
545 aicp->ai_link_ammo_levels_always[i] = FLT_MIN;
546 aicp->ai_primary_ammo_burst_mult[i] = FLT_MIN;
547 aicp->ai_link_energy_levels_maybe[i] = FLT_MIN;
548 aicp->ai_link_energy_levels_always[i] = FLT_MIN;
549 aicp->ai_predict_position_delay[i] = INT_MIN;
550 aicp->ai_shield_manage_delay[i] = FLT_MIN;
551 aicp->ai_ship_fire_delay_scale_friendly[i] = FLT_MIN;
552 aicp->ai_ship_fire_delay_scale_hostile[i] = FLT_MIN;
553 aicp->ai_ship_fire_secondary_delay_scale_friendly[i] = FLT_MIN;
554 aicp->ai_ship_fire_secondary_delay_scale_hostile[i] = FLT_MIN;
555 aicp->ai_turn_time_scale[i] = FLT_MIN;
556 aicp->ai_glide_attack_percent[i] = FLT_MIN;
557 aicp->ai_circle_strafe_percent[i] = FLT_MIN;
558 aicp->ai_glide_strafe_percent[i] = FLT_MIN;
559 aicp->ai_random_sidethrust_percent[i] = FLT_MIN;
560 aicp->ai_stalemate_time_thresh[i] = FLT_MIN;
561 aicp->ai_stalemate_dist_thresh[i] = FLT_MIN;
562 aicp->ai_chance_to_use_missiles_on_plr[i] = INT_MIN;
563 aicp->ai_max_aim_update_delay[i] = FLT_MIN;
564 aicp->ai_turret_max_aim_update_delay[i] = FLT_MIN;
565 }
566 aicp->ai_profile_flags.reset();
567 aicp->ai_profile_flags_set.reset();
568
569 //AI Class autoscale overrides
570 //INT_MIN and FLT_MIN represent the "not set" state
571 for (i = 0; i < NUM_SKILL_LEVELS; i++)
572 {
573 aicp->ai_aburn_use_factor[i] = INT_MIN;
574 aicp->ai_shockwave_evade_chance[i] = FLT_MIN;
575 aicp->ai_get_away_chance[i] = FLT_MIN;
576 aicp->ai_secondary_range_mult[i] = FLT_MIN;
577 }
578 aicp->ai_class_autoscale = true; //Retail behavior is to do the stupid autoscaling
579 }
580
set_aic_flag(ai_class * aicp,const char * name,AI::Profile_Flags flag)581 void set_aic_flag(ai_class *aicp, const char *name, AI::Profile_Flags flag)
582 {
583 auto flags = &(aicp->ai_profile_flags);
584 auto set = &(aicp->ai_profile_flags_set);
585
586 if (optional_string(name))
587 {
588 bool val;
589 stuff_boolean(&val);
590
591 flags->set(flag, val);
592
593 set->set(flag);
594 }
595 else
596 set->remove(flag);
597 }
598
parse_ai_class()599 void parse_ai_class()
600 {
601 ai_class *aicp = &Ai_classes[Num_ai_classes];
602
603 init_ai_class(aicp);
604
605 required_string("$Name:");
606 stuff_string(aicp->name, F_NAME, NAME_LENGTH);
607
608 Ai_class_names[Num_ai_classes] = aicp->name;
609
610 required_string("$accuracy:");
611 parse_float_list(aicp->ai_accuracy, NUM_SKILL_LEVELS);
612
613 required_string("$evasion:");
614 parse_float_list(aicp->ai_evasion, NUM_SKILL_LEVELS);
615
616 required_string("$courage:");
617 parse_float_list(aicp->ai_courage, NUM_SKILL_LEVELS);
618
619 required_string("$patience:");
620 parse_float_list(aicp->ai_patience, NUM_SKILL_LEVELS);
621
622 if (optional_string("$Afterburner Use Factor:"))
623 parse_int_list(aicp->ai_aburn_use_factor, NUM_SKILL_LEVELS);
624
625 if (optional_string("$Shockwave Evade Chances Per Second:"))
626 parse_float_list(aicp->ai_shockwave_evade_chance, NUM_SKILL_LEVELS);
627
628 if (optional_string("$Get Away Chance:"))
629 parse_float_list(aicp->ai_get_away_chance, NUM_SKILL_LEVELS);
630
631 if (optional_string("$Secondary Range Multiplier:"))
632 parse_float_list(aicp->ai_secondary_range_mult, NUM_SKILL_LEVELS);
633
634 if (optional_string("$Autoscale by AI Class Index:"))
635 stuff_boolean(&aicp->ai_class_autoscale);
636
637 //Parse optional values for stuff imported from ai_profiles
638 if (optional_string("$AI Countermeasure Firing Chance:"))
639 parse_float_list(aicp->ai_cmeasure_fire_chance, NUM_SKILL_LEVELS);
640
641 if (optional_string("$AI In Range Time:"))
642 parse_float_list(aicp->ai_in_range_time, NUM_SKILL_LEVELS);
643
644 if (optional_string("$AI Always Links Ammo Weapons:"))
645 parse_float_list(aicp->ai_link_ammo_levels_always, NUM_SKILL_LEVELS);
646
647 if (optional_string("$AI Maybe Links Ammo Weapons:"))
648 parse_float_list(aicp->ai_link_ammo_levels_maybe, NUM_SKILL_LEVELS);
649
650 if (optional_string("$Primary Ammo Burst Multiplier:"))
651 parse_float_list(aicp->ai_primary_ammo_burst_mult, NUM_SKILL_LEVELS);
652
653 if (optional_string("$AI Always Links Energy Weapons:"))
654 parse_float_list(aicp->ai_link_energy_levels_always, NUM_SKILL_LEVELS);
655
656 if (optional_string("$AI Maybe Links Energy Weapons:"))
657 parse_float_list(aicp->ai_link_energy_levels_maybe, NUM_SKILL_LEVELS);
658
659 // represented in fractions of F1_0
660 if (optional_string("$Predict Position Delay:")) {
661 int iLoop;
662 float temp_list[NUM_SKILL_LEVELS];
663 parse_float_list(temp_list, NUM_SKILL_LEVELS);
664 for (iLoop = 0; iLoop < NUM_SKILL_LEVELS; iLoop++)
665 aicp->ai_predict_position_delay[iLoop] = fl2f(temp_list[iLoop]);
666 }
667
668 if (optional_string("$AI Shield Manage Delay:") || optional_string("$AI Shield Manage Delays:"))
669 parse_float_list(aicp->ai_shield_manage_delay, NUM_SKILL_LEVELS);
670
671 if (optional_string("$Friendly AI Fire Delay Scale:"))
672 parse_float_list(aicp->ai_ship_fire_delay_scale_friendly, NUM_SKILL_LEVELS);
673
674 if (optional_string("$Hostile AI Fire Delay Scale:"))
675 parse_float_list(aicp->ai_ship_fire_delay_scale_hostile, NUM_SKILL_LEVELS);
676
677 if (optional_string("$Friendly AI Secondary Fire Delay Scale:"))
678 parse_float_list(aicp->ai_ship_fire_secondary_delay_scale_friendly, NUM_SKILL_LEVELS);
679
680 if (optional_string("$Hostile AI Secondary Fire Delay Scale:"))
681 parse_float_list(aicp->ai_ship_fire_secondary_delay_scale_hostile, NUM_SKILL_LEVELS);
682
683 if (optional_string("$AI Turn Time Scale:"))
684 parse_float_list(aicp->ai_turn_time_scale, NUM_SKILL_LEVELS);
685
686 if (optional_string("$Glide Attack Percent:")) {
687 parse_float_list(aicp->ai_glide_attack_percent, NUM_SKILL_LEVELS);
688 for (int i = 0; i < NUM_SKILL_LEVELS; i++) {
689 if (aicp->ai_glide_attack_percent[i] < 0.0f || aicp->ai_glide_attack_percent[i] > 100.0f) {
690 aicp->ai_glide_attack_percent[i] = 0.0f;
691 Warning(LOCATION, "$Glide Attack Percent should be between 0 and 100.0 (read %f). Setting to 0.", aicp->ai_glide_attack_percent[i]);
692 }
693 aicp->ai_glide_attack_percent[i] /= 100.0;
694 }
695
696 }
697
698 if (optional_string("$Circle Strafe Percent:")) {
699 parse_float_list(aicp->ai_circle_strafe_percent, NUM_SKILL_LEVELS);
700 for (int i = 0; i < NUM_SKILL_LEVELS; i++) {
701 if (aicp->ai_circle_strafe_percent[i] < 0.0f || aicp->ai_circle_strafe_percent[i] > 100.0f) {
702 aicp->ai_circle_strafe_percent[i] = 0.0f;
703 Warning(LOCATION, "$Circle Strafe Percent should be between 0 and 100.0 (read %f). Setting to 0.", aicp->ai_circle_strafe_percent[i]);
704 }
705 aicp->ai_circle_strafe_percent[i] /= 100.0;
706 }
707 }
708
709 if (optional_string("$Glide Strafe Percent:")) {
710 parse_float_list(aicp->ai_glide_strafe_percent, NUM_SKILL_LEVELS);
711 for (int i = 0; i < NUM_SKILL_LEVELS; i++) {
712 if (aicp->ai_glide_strafe_percent[i] < 0.0f || aicp->ai_glide_strafe_percent[i] > 100.0f) {
713 aicp->ai_glide_strafe_percent[i] = 0.0f;
714 Warning(LOCATION, "$Glide Strafe Percent should be between 0 and 100.0 (read %f). Setting to 0.", aicp->ai_glide_strafe_percent[i]);
715 }
716 aicp->ai_glide_strafe_percent[i] /= 100.0;
717 }
718 }
719
720 if (optional_string("$Random Sidethrust Percent:")) {
721 parse_float_list(aicp->ai_random_sidethrust_percent, NUM_SKILL_LEVELS);
722 for (int i = 0; i < NUM_SKILL_LEVELS; i++) {
723 if (aicp->ai_random_sidethrust_percent[i] < 0.0f || aicp->ai_random_sidethrust_percent[i] > 100.0f) {
724 aicp->ai_random_sidethrust_percent[i] = 0.0f;
725 Warning(LOCATION, "$Random Sidethrust Percent should be between 0 and 100.0 (read %f). Setting to 0.", aicp->ai_random_sidethrust_percent[i]);
726 }
727 aicp->ai_random_sidethrust_percent[i] /= 100.0;
728 }
729 }
730
731 if (optional_string("$Stalemate Time Threshold:"))
732 parse_float_list(aicp->ai_stalemate_time_thresh, NUM_SKILL_LEVELS);
733
734 if (optional_string("$Stalemate Distance Threshold:"))
735 parse_float_list(aicp->ai_stalemate_dist_thresh, NUM_SKILL_LEVELS);
736
737 if (optional_string("$Chance AI Has to Fire Missiles at Player:"))
738 parse_int_list(aicp->ai_chance_to_use_missiles_on_plr, NUM_SKILL_LEVELS);
739
740 if (optional_string("$Max Aim Update Delay:"))
741 parse_float_list(aicp->ai_max_aim_update_delay, NUM_SKILL_LEVELS);
742
743 if (optional_string("$Turret Max Aim Update Delay:"))
744 parse_float_list(aicp->ai_turret_max_aim_update_delay, NUM_SKILL_LEVELS);
745
746 set_aic_flag(aicp, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships);
747
748 set_aic_flag(aicp, "$smart primary weapon selection:", AI::Profile_Flags::Smart_primary_weapon_selection);
749
750 set_aic_flag(aicp, "$smart secondary weapon selection:", AI::Profile_Flags::Smart_secondary_weapon_selection);
751
752 set_aic_flag(aicp, "$smart shield management:", AI::Profile_Flags::Smart_shield_management);
753
754 set_aic_flag(aicp, "$smart afterburner management:", AI::Profile_Flags::Smart_afterburner_management);
755
756 set_aic_flag(aicp, "$free afterburner use:", AI::Profile_Flags::Free_afterburner_use);
757
758 set_aic_flag(aicp, "$allow rapid secondary dumbfire:", AI::Profile_Flags::Allow_rapid_secondary_dumbfire);
759
760 set_aic_flag(aicp, "$huge turret weapons ignore bombs:", AI::Profile_Flags::Huge_turret_weapons_ignore_bombs);
761
762 set_aic_flag(aicp, "$don't insert random turret fire delay:", AI::Profile_Flags::Dont_insert_random_turret_fire_delay);
763
764 set_aic_flag(aicp, "$prevent turrets targeting too distant bombs:", AI::Profile_Flags::Prevent_targeting_bombs_beyond_range);
765
766 set_aic_flag(aicp, "$smart subsystem targeting for turrets:", AI::Profile_Flags::Smart_subsystem_targeting_for_turrets);
767
768 set_aic_flag(aicp, "$allow turrets target weapons freely:", AI::Profile_Flags::Allow_turrets_target_weapons_freely);
769
770 set_aic_flag(aicp, "$allow vertical dodge:", AI::Profile_Flags::Allow_vertical_dodge);
771
772 set_aic_flag(aicp, "$no extra collision avoidance vs player:", AI::Profile_Flags::No_special_player_avoid);
773
774 set_aic_flag(aicp, "$all ships manage shields:", AI::Profile_Flags::All_ships_manage_shields);
775
776 set_aic_flag(aicp, "$ai can slow down when attacking big ships:", AI::Profile_Flags::Ai_can_slow_down_attacking_big_ships);
777
778 set_aic_flag(aicp, "$use actual primary range:", AI::Profile_Flags::Use_actual_primary_range);
779 }
780
reset_ai_class_names()781 void reset_ai_class_names()
782 {
783 ai_class *aicp;
784
785 for (int i = 0; i < Num_ai_classes; i++) {
786 aicp = &Ai_classes[i];
787
788 Ai_class_names[i] = aicp->name;
789 }
790 }
791
792 #define AI_CLASS_INCREMENT 10
parse_aitbl()793 void parse_aitbl()
794 {
795 try {
796 read_file_text("ai.tbl", CF_TYPE_TABLES);
797 reset_parse();
798
799 //Just in case parse_aitbl is called twice
800 free_ai_stuff();
801
802 Num_ai_classes = 0;
803 Num_alloced_ai_classes = AI_CLASS_INCREMENT;
804 Ai_classes = (ai_class*) vm_malloc(Num_alloced_ai_classes * sizeof(ai_class));
805 Ai_class_names = (char**) vm_malloc(Num_alloced_ai_classes * sizeof(char*));
806
807 required_string("#AI Classes");
808
809 while (required_string_either("#End", "$Name:")) {
810
811 parse_ai_class();
812
813 Num_ai_classes++;
814
815 if(Num_ai_classes >= Num_alloced_ai_classes)
816 {
817 Num_alloced_ai_classes += AI_CLASS_INCREMENT;
818 Ai_classes = (ai_class*) vm_realloc(Ai_classes, Num_alloced_ai_classes * sizeof(ai_class));
819
820 // Ai_class_names doesn't realloc all that well so we have to do it the hard way.
821 // Luckily, it's contents can be easily replaced so we don't have to save anything.
822 vm_free(Ai_class_names);
823 Ai_class_names = (char **) vm_malloc(Num_alloced_ai_classes * sizeof(char*));
824 reset_ai_class_names();
825 }
826 }
827
828 atexit(free_ai_stuff);
829 }
830 catch (const parse::ParseException& e)
831 {
832 mprintf(("TABLES: Unable to parse 'ai.tbl'! Error message = %s.\n", e.what()));
833 return;
834 }
835 }
836
837 LOCAL int ai_inited = 0;
838
839 //========================= BOOK-KEEPING FUNCTIONS =======================
840
841 /**
842 * Called once at game start-up
843 */
ai_init()844 void ai_init()
845 {
846 if ( !ai_inited ) {
847 // Do the first time initialization stuff here
848 try
849 {
850 parse_aitbl();
851 }
852 catch (const parse::ParseException& e)
853 {
854 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "ai.tbl", e.what()));
855 }
856
857 ai_inited = 1;
858 }
859
860 init_semirand();
861
862 ai_level_init();
863 }
864
865 /**
866 * This inits the AI. You should be able to call this between
867 * levels to reset everything.
868 */
ai_level_init()869 void ai_level_init()
870 {
871 int i;
872
873 // Do the stuff to reset all ai stuff here
874 for (i=0; i<MAX_AI_INFO ; i++) {
875 Ai_info[i].shipnum = -1;
876 }
877
878 Ai_goal_signature = 0;
879
880 for (i = 0; i < Num_iffs; i++)
881 Iff_info[i].ai_rearm_timestamp = timestamp(-1);
882
883 // clear out the stuff needed for AI firing powerful secondary weapons
884 ai_init_secondary_info();
885
886 Ai_last_arrive_path=0;
887 }
888
889 // BEGIN STEALTH
890 // -----------------------------------------------------------------------------
891
892 /**
893 * Check if object is a stealth ship
894 */
is_object_stealth_ship(object * objp)895 bool is_object_stealth_ship(object* objp)
896 {
897 if (objp->type == OBJ_SHIP) {
898 if (Ships[objp->instance].flags[Ship::Ship_Flags::Stealth]) {
899 return true;
900 }
901 }
902
903 // not stealth ship
904 return false;
905 }
906
907 /**
908 * Init necessary AI info for new stealth target
909 */
init_ai_stealth_info(ai_info * aip,object * stealth_objp)910 void init_ai_stealth_info(ai_info *aip, object *stealth_objp)
911 {
912 Assert(is_object_stealth_ship(stealth_objp));
913
914 // set necessary ai info for new stealth target
915 aip->stealth_last_pos = stealth_objp->pos;
916 aip->stealth_velocity = stealth_objp->phys_info.vel;
917 aip->stealth_last_visible_stamp = timestamp();
918 }
919
920 // -----------------------------------------------------------------------------
921 // Check whether Pl_objp can see a stealth ship object
922 #define STEALTH_NOT_IN_FRUSTUM 0
923 #define STEALTH_IN_FRUSTUM 1
924 #define STEALTH_FULLY_TARGETABLE 2
925
926 /**
927 * Return dist scaler based on skill level
928 */
get_skill_stealth_dist_scaler()929 float get_skill_stealth_dist_scaler()
930 {
931 switch (Game_skill_level) {
932 case 0: // very easy
933 return 0.65f;
934
935 case 1: // easy
936 return 0.9f;
937
938 case 2: // medium
939 return 1.0f;
940
941 case 3: // hard
942 return 1.1f;
943
944 case 4: // insane
945 return 1.3f;
946
947 default:
948 UNREACHABLE("An invalid difficulty level of %d was passed to get_skill_stealth_dist_scaler().", Game_skill_level);
949
950 }
951
952 return 1.0f;
953 }
954
955 /**
956 * Return multiplier on dot based on skill level
957 */
get_skill_stealth_dot_scaler()958 float get_skill_stealth_dot_scaler()
959 {
960 switch (Game_skill_level) {
961 case 0: // very easy
962 return 1.3f;
963
964 case 1: // easy
965 return 1.1f;
966
967 case 2: // medium
968 return 1.0f;
969
970 case 3: // hard
971 return 0.9f;
972
973 case 4: // insane
974 return 0.7f;
975
976 default:
977 UNREACHABLE("An invalid difficulty level of %d was passed to get_skill_stealth_dot_scaler().", Game_skill_level);
978 }
979
980 return 1.0f;
981 }
982
ai_is_stealth_visible(object * viewer_objp,object * stealth_objp)983 int ai_is_stealth_visible(object *viewer_objp, object *stealth_objp)
984 {
985 ship *shipp;
986 vec3d vec_to_stealth;
987 float dot_to_stealth, dist_to_stealth, max_stealth_dist;
988
989 Assert(stealth_objp->type == OBJ_SHIP);
990 shipp = &Ships[stealth_objp->instance];
991 Assert(viewer_objp->type == OBJ_SHIP);
992
993 // check if stealth ship
994 Assert(shipp->flags[Ship::Ship_Flags::Stealth]);
995
996 // check if in neb and below awac level for visible
997 if ( !ship_is_visible_by_team(stealth_objp, &Ships[viewer_objp->instance]) ) {
998 vm_vec_sub(&vec_to_stealth, &stealth_objp->pos, &viewer_objp->pos);
999 dist_to_stealth = vm_vec_mag_quick(&vec_to_stealth);
1000 dot_to_stealth = vm_vec_dot(&viewer_objp->orient.vec.fvec, &vec_to_stealth) / dist_to_stealth;
1001
1002 // get max dist at which stealth is visible
1003 max_stealth_dist = get_skill_stealth_dist_scaler() * STEALTH_MAX_VIEW_DIST;
1004
1005 // now check if within view frustum
1006 float needed_dot_to_stealth;
1007 if (dist_to_stealth < 100) {
1008 needed_dot_to_stealth = 0.0f;
1009 } else {
1010 needed_dot_to_stealth = get_skill_stealth_dot_scaler() * float(STEALTH_VIEW_CONE_DOT) * (dist_to_stealth / max_stealth_dist);
1011 }
1012 if (dot_to_stealth > needed_dot_to_stealth) {
1013 if (dist_to_stealth < max_stealth_dist) {
1014 return STEALTH_IN_FRUSTUM;
1015 }
1016 }
1017
1018 // not within frustum
1019 return STEALTH_NOT_IN_FRUSTUM;
1020 }
1021
1022 // visible by awacs level
1023 return STEALTH_FULLY_TARGETABLE;
1024 }
1025
1026 // END STEALTH
1027
1028 /**
1029 * Compute dot product of direction vector and forward vector.
1030 * Direction vector is vector from one object to other object.
1031 * Forward vector is the forward vector of the ship.
1032 * If from_dot == NULL, don't fill it in.
1033 */
compute_dots(object * objp,object * other_objp,float * to_dot,float * from_dot)1034 float compute_dots(object *objp, object *other_objp, float *to_dot, float *from_dot)
1035 {
1036 vec3d v2o;
1037 float dist;
1038
1039 dist = vm_vec_normalized_dir(&v2o, &other_objp->pos, &objp->pos);
1040
1041 *to_dot = vm_vec_dot(&objp->orient.vec.fvec, &v2o);
1042
1043 if (from_dot != NULL)
1044 *from_dot = - vm_vec_dot(&other_objp->orient.vec.fvec, &v2o);
1045
1046 return dist;
1047 }
1048
1049 /**
1050 * Update estimated stealth info
1051 *
1052 * This is a "cheat" update. Error increases with time not seen, true distance away, dot to enemy
1053 * this is done only if we can not see the stealth target
1054 * need to infer its position either by weapon fire pos or last know pos
1055 */
update_ai_stealth_info_with_error(ai_info * aip)1056 void update_ai_stealth_info_with_error(ai_info *aip)
1057 {
1058 object *stealth_objp;
1059
1060 // make sure I am targeting a stealth ship
1061 Assert( is_object_stealth_ship(&Objects[aip->target_objnum]) );
1062 stealth_objp = &Objects[aip->target_objnum];
1063
1064 // if update is due to weapon fire, get exact stealth position
1065 aip->stealth_last_pos = stealth_objp->pos;
1066 aip->stealth_velocity = stealth_objp->phys_info.vel;
1067 aip->stealth_last_visible_stamp = timestamp();
1068 }
1069
1070 /**
1071 * Update danger_weapon_objnum and signature in ai_info to say this weapon is to be avoided.
1072 */
ai_update_danger_weapon(int attacked_objnum,int weapon_objnum)1073 void ai_update_danger_weapon(int attacked_objnum, int weapon_objnum)
1074 {
1075 object *objp, *weapon_objp;
1076 ai_info *aip;
1077 float old_dist, new_dist;
1078 float old_dot, new_dot;
1079 object *old_weapon_objp = NULL;
1080
1081 // any object number less than 0 is invalid
1082 if ((attacked_objnum < 0) || (weapon_objnum < 0)) {
1083 return;
1084 }
1085
1086 objp = &Objects[attacked_objnum];
1087
1088 // AL 2-24-98: If this isn't a ship, we don't need to worry about updating weapon_objnum (ie it would be
1089 // an asteroid or bomb).
1090 if ( objp->type != OBJ_SHIP ) {
1091 return;
1092 }
1093
1094 weapon_objp = &Objects[weapon_objnum];
1095
1096 aip = &Ai_info[Ships[objp->instance].ai_index];
1097
1098 // if my target is a stealth ship and is not visible
1099 if (aip->target_objnum >= 0) {
1100 if ( is_object_stealth_ship(&Objects[aip->target_objnum]) ) {
1101 if ( ai_is_stealth_visible(objp, &Objects[aip->target_objnum]) == STEALTH_NOT_IN_FRUSTUM ) {
1102 // and the weapon is coming from that stealth ship
1103 if (weapon_objp->parent == aip->target_objnum) {
1104 // update my position estimate for stealth ship
1105 update_ai_stealth_info_with_error(aip);
1106 }
1107 }
1108 }
1109 }
1110
1111 if (aip->danger_weapon_objnum != -1) {
1112 old_weapon_objp = &Objects[aip->danger_weapon_objnum];
1113 if ((old_weapon_objp->type == OBJ_WEAPON) && (old_weapon_objp->signature == aip->danger_weapon_signature)) {
1114 ;
1115 } else {
1116 aip->danger_weapon_objnum = -1;
1117 }
1118 }
1119
1120 new_dist = compute_dots(weapon_objp, objp, &new_dot, NULL);
1121
1122 if (aip->danger_weapon_objnum == -1) {
1123 if (new_dist < 1500.0f) {
1124 if (new_dot > 0.5f) {
1125 aip->danger_weapon_objnum = weapon_objnum;
1126 aip->danger_weapon_signature = weapon_objp->signature;
1127 }
1128 }
1129 } else {
1130 Assert(old_weapon_objp != NULL);
1131 old_dist = compute_dots(old_weapon_objp, objp, &old_dot, NULL);
1132
1133 if (old_dot < 0.5f) {
1134 aip->danger_weapon_objnum = -1;
1135 old_dist = 9999.9f;
1136 }
1137
1138 if ((new_dot > 0.5f) && (new_dot > old_dot-0.01f)) {
1139 if (new_dist < old_dist) {
1140 aip->danger_weapon_objnum = weapon_objnum;
1141 aip->danger_weapon_signature = weapon_objp->signature;
1142 }
1143 }
1144 }
1145 }
1146
1147 // Asteroth - Manipulates retail inputs to produce the outputs that would match retail behavior
1148 // vel and acc_limit should already be sent in with retail values
1149 // A more in-depth explanation can be found in the #2740 PR description
ai_compensate_for_retail_turning(vec3d * vel_limit,vec3d * acc_limit,float rotdamp,bool is_weapon)1150 void ai_compensate_for_retail_turning(vec3d* vel_limit, vec3d* acc_limit, float rotdamp, bool is_weapon) {
1151
1152 if (is_weapon) {
1153 // Missiles accelerated instantly technically, but this should do.
1154 *acc_limit = *vel_limit * 1000.f;
1155 // Approximately what you'd get at 60fps
1156 *vel_limit *= 0.128f;
1157 return;
1158 }
1159
1160 // If we're a ship instead things get a bit hairier
1161 // This is the polynomial approximation of the 'combination' effects of
1162 // angular_move and physics_sim_rot
1163 for (int i = 0; i < 3; i++) {
1164 float& vel = vel_limit->a1d[i];
1165 float& acc = acc_limit->a1d[i];
1166
1167 float v1 = 2 * acc * rotdamp;
1168 float v2 = 2 * vel;
1169 if (v1 <= v2) {
1170 vel = v1;
1171 }
1172 else {
1173 vel = v2;
1174 if (v1 == 0)
1175 acc = 1000.f;
1176 else
1177 acc = acc * (2 - v2 / v1);
1178 }
1179 }
1180 }
1181
1182 // If rvec != NULL, use it to match bank by calling vm_angular_move_matrix.
1183 // (rvec defaults to NULL)
1184 // optionally specified turnrate_mod contains multipliers for each component of the turnrate (RATE, so 0.5 = slower)
ai_turn_towards_vector(vec3d * dest,object * objp,vec3d * slide_vec,vec3d * rel_pos,float bank_override,int flags,vec3d * rvec,vec3d * turnrate_mod)1185 void ai_turn_towards_vector(vec3d* dest, object* objp, vec3d* slide_vec, vec3d* rel_pos, float bank_override, int flags, vec3d* rvec, vec3d* turnrate_mod)
1186 {
1187 matrix curr_orient;
1188 vec3d vel_in, vel_out, desired_fvec, src;
1189 float delta_time;
1190 physics_info *pip;
1191 float delta_bank;
1192
1193 Assertion(objp->type == OBJ_SHIP || objp->type == OBJ_WEAPON, "ai_turn_towards_vector called on a non-ship non-weapon object!");
1194
1195 // Don't allow a ship to turn if it has no engine strength.
1196 // AL 3-12-98: objp may not always be a ship!
1197 if ( (objp->type == OBJ_SHIP) && !(flags & AITTV_VIA_SEXP) ) {
1198 if (ship_get_subsystem_strength(&Ships[objp->instance], SUBSYSTEM_ENGINE) <= 0.0f)
1199 return;
1200 }
1201
1202 // Don't allow a ship to turn if it's immobile.
1203 if ( objp->flags[Object::Object_Flags::Immobile] ) {
1204 return;
1205 }
1206
1207 pip = &objp->phys_info;
1208
1209 vel_in = pip->rotvel;
1210 curr_orient = objp->orient;
1211 delta_time = flFrametime;
1212
1213 vec3d vel_limit, acc_limit;
1214 vm_vec_zero(&vel_limit);
1215
1216 // get the turn rate if we have Use_axial_turnrate_differences
1217 if (objp->type == OBJ_SHIP && The_mission.ai_profile->flags[AI::Profile_Flags::Use_axial_turnrate_differences]) {
1218 vel_limit = Ship_info[Ships[objp->instance].ship_info_index].max_rotvel;
1219
1220 } else { // else get the turn time
1221 float turn_time = 0.f;
1222 if (objp->type == OBJ_WEAPON) {
1223 turn_time = Weapon_info[Weapons[objp->instance].weapon_info_index].turn_time;
1224 }
1225 else if (objp->type == OBJ_SHIP) {
1226 turn_time = Ship_info[Ships[objp->instance].ship_info_index].srotation_time;
1227 }
1228
1229 //and then turn it into turnrate
1230 if (turn_time > 0.0f)
1231 {
1232 vel_limit.xyz.x = PI2 / turn_time;
1233 vel_limit.xyz.y = PI2 / turn_time;
1234 vel_limit.xyz.z = PI2 / turn_time;
1235 }
1236 }
1237
1238 // maybe modify the ship's turnrate if caller wants
1239 if (turnrate_mod) {
1240 vel_limit.xyz.x *= turnrate_mod->xyz.x;
1241 vel_limit.xyz.y *= turnrate_mod->xyz.y;
1242 vel_limit.xyz.z *= turnrate_mod->xyz.z;
1243 }
1244
1245
1246 // Scale turnrate based on skill level and team.
1247 if (!(flags & AITTV_FAST) && !(flags & AITTV_VIA_SEXP)) {
1248 if (objp->type == OBJ_SHIP) {
1249 if (iff_x_attacks_y(Player_ship->team, Ships[objp->instance].team)) {
1250 if (Ai_info[Ships[objp->instance].ai_index].ai_turn_time_scale != 0.f) {
1251 vm_vec_scale(&vel_limit, 1/Ai_info[Ships[objp->instance].ai_index].ai_turn_time_scale);
1252 }
1253 }
1254 }
1255 }
1256
1257 // Set rate at which ship can accelerate to its rotational velocity.
1258 // For now, weapons just go much faster.
1259 acc_limit = vel_limit;
1260 if (objp->type == OBJ_WEAPON)
1261 acc_limit *= 8.0f;
1262
1263 // do _proper_ handling of acc_limit and vel_limit if this flag is set
1264 if (Framerate_independent_turning) {
1265 // handle modifications to rotdamp (that could affect acc_limit and vel_limit)
1266 // handled in its entirety in physics_sim_rot
1267 float rotdamp = pip->rotdamp;
1268 float shock_fraction_time_left = 0.f;
1269 if (pip->flags & PF_IN_SHOCKWAVE) {
1270 shock_fraction_time_left = timestamp_until(pip->shockwave_decay) / (float)SW_BLAST_DURATION;
1271 if (shock_fraction_time_left > 0)
1272 rotdamp = pip->rotdamp + pip->rotdamp * (SW_ROT_FACTOR - 1) * shock_fraction_time_left;
1273 }
1274
1275 if (Ai_respect_tabled_turntime_rotdamp) {
1276 // We're assuming the turn rate here is accurate so leave it as is
1277 // but we'll use the ship's rotdamp to modify acc_limit
1278 // (this is the polynomial approximation of the exponential effect rotdamp has on players)
1279 if (rotdamp == 0.f)
1280 acc_limit *= 1000.f;
1281 else
1282 acc_limit = vel_limit * (0.5f / rotdamp);
1283 }
1284 else {
1285 // else, calculate the retail-friendly vel and acc values
1286 ai_compensate_for_retail_turning(&vel_limit, &acc_limit, rotdamp, objp->type == OBJ_WEAPON);
1287
1288 // handle missile turning accel (which is bundled into rotdamp)
1289 if (objp->type == OBJ_WEAPON && rotdamp != 0.f) {
1290 acc_limit = vel_limit * (0.5f / rotdamp);
1291 }
1292 }
1293 }
1294
1295 // for formation flying
1296 if ((flags & AITTV_SLOW_BANK_ACCEL)) {
1297 acc_limit.xyz.z *= 0.2f;
1298 }
1299
1300 src = objp->pos;
1301
1302 if (rel_pos != NULL) {
1303 vec3d gun_point;
1304 vm_vec_unrotate(&gun_point, rel_pos, &objp->orient);
1305 vm_vec_add2(&src, &gun_point);
1306 }
1307
1308 vm_vec_normalized_dir(&desired_fvec, dest, &src);
1309
1310 // Since ship isn't necessarily moving in the direction it's pointing, sometimes it's better
1311 // to be moving towards goal rather than just pointing. So, if slide_vec is !NULL, try to
1312 // make ship move towards goal, not point at goal.
1313 if (slide_vec != NULL) {
1314 vm_vec_add2(&desired_fvec, slide_vec);
1315 vm_vec_normalize(&desired_fvec);
1316 }
1317
1318 // Should be more general case here. Currently, anything that is not a weapon will bank when it turns.
1319 // Goober5000 - don't bank if sexp or ship says not to
1320 if ( (objp->type == OBJ_WEAPON) || (flags & AITTV_IGNORE_BANK ) )
1321 delta_bank = 0.0f;
1322 else if (objp->type == OBJ_SHIP && Ship_info[Ships[objp->instance].ship_info_index].flags[Ship::Info_Flags::Dont_bank_when_turning])
1323 delta_bank = 0.0f;
1324 else if ((bank_override) && (iff_x_attacks_y(Ships[objp->instance].team, Player_ship->team))) { // Theoretically, this will only happen for Shivans.
1325 delta_bank = bank_override;
1326 } else {
1327 delta_bank = vm_vec_dot(&curr_orient.vec.rvec, &objp->last_orient.vec.rvec);
1328 delta_bank = 200.0f * (1.0f - delta_bank) * pip->delta_bank_const;
1329 if (vm_vec_dot(&objp->last_orient.vec.fvec, &objp->orient.vec.rvec) < 0.0f)
1330 delta_bank = -delta_bank;
1331 }
1332
1333
1334 matrix out_orient;
1335
1336 if (rvec != NULL) {
1337 matrix goal_orient;
1338
1339 vm_vector_2_matrix(&goal_orient, &desired_fvec, NULL, rvec);
1340 vm_angular_move_matrix(&goal_orient, &curr_orient, &vel_in, delta_time,
1341 &out_orient, &vel_out, &vel_limit, &acc_limit, The_mission.ai_profile->flags[AI::Profile_Flags::No_turning_directional_bias]);
1342 } else {
1343 vm_angular_move_forward_vec(&desired_fvec, &curr_orient, &vel_in, delta_time, delta_bank,
1344 &out_orient, &vel_out, &vel_limit, &acc_limit, The_mission.ai_profile->flags[AI::Profile_Flags::No_turning_directional_bias]);
1345 }
1346
1347 if (Framerate_independent_turning) {
1348 if (IS_MAT_NULL(&pip->ai_desired_orient)) {
1349 pip->ai_desired_orient = out_orient;
1350 pip->desired_rotvel = vel_out;
1351 } // if ai_desired_orient is NOT null, that means a SEXP is trying to move the ship right now, and the AI should defer to it
1352 }
1353 else {
1354 objp->orient = out_orient;
1355 pip->rotvel = vel_out;
1356 }
1357
1358 }
1359
1360 // Set aip->target_objnum to objnum
1361 // Update aip->previous_target_objnum.
1362 // If new target (objnum) is different than old target, reset target_time.
set_target_objnum(ai_info * aip,int objnum)1363 int set_target_objnum(ai_info *aip, int objnum)
1364 {
1365 if ((aip != Player_ai) && (!timestamp_elapsed(aip->ok_to_target_timestamp))) {
1366 return aip->target_objnum;
1367 }
1368
1369 if (aip->target_objnum == objnum) {
1370 aip->previous_target_objnum = aip->target_objnum;
1371 } else {
1372 aip->previous_target_objnum = aip->target_objnum;
1373
1374 // ignore this assert if a multiplayer observer
1375 if((Game_mode & GM_MULTIPLAYER) && (aip == Player_ai) && (Player_obj->type == OBJ_OBSERVER)){
1376 } else {
1377 Assert(objnum != Ships[aip->shipnum].objnum); // make sure not targeting self
1378 }
1379
1380 // if stealth target, init ai_info for stealth
1381 if ( (objnum >= 0) && is_object_stealth_ship(&Objects[objnum]) ) {
1382 init_ai_stealth_info(aip, &Objects[objnum]);
1383 }
1384
1385 aip->target_objnum = objnum;
1386 aip->target_time = 0.0f;
1387 aip->time_enemy_near = 0.0f; //SUSHI: Reset the "time near" counter whenever the target is changed
1388 aip->last_hit_target_time = Missiontime; //Also, the "last hit target" time should be reset when the target changes
1389 aip->target_signature = (objnum >= 0) ? Objects[objnum].signature : -1;
1390 // clear targeted subsystem
1391 set_targeted_subsys(aip, NULL, -1);
1392 }
1393
1394 return aip->target_objnum;
1395 }
1396
1397 int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags);
1398
1399 /**
1400 * Make new_subsys the targeted subsystem of ship *aip.
1401 */
set_targeted_subsys(ai_info * aip,ship_subsys * new_subsys,int parent_objnum)1402 ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int parent_objnum)
1403 {
1404 Assert(aip != NULL);
1405
1406 aip->last_subsys_target = aip->targeted_subsys;
1407 aip->targeted_subsys = new_subsys;
1408 aip->targeted_subsys_parent = parent_objnum;
1409
1410 if ( new_subsys ) {
1411 // Make new_subsys target
1412 if (new_subsys->system_info->type == SUBSYSTEM_ENGINE) {
1413 if ( aip != Player_ai ) {
1414 Assert( aip->shipnum >= 0 );
1415 ai_select_primary_weapon(&Objects[Ships[aip->shipnum].objnum], &Objects[parent_objnum], Weapon::Info_Flags::Puncture);
1416 ship_primary_changed(&Ships[aip->shipnum]); // AL: maybe send multiplayer information when AI ship changes primaries
1417 }
1418 }
1419
1420 if ( aip == Player_ai ) {
1421 if (aip->last_subsys_target != aip->targeted_subsys) {
1422 hud_lock_reset(0.5f);
1423 }
1424 }
1425
1426 } else {
1427 // Cleanup any subsys path information if it exists
1428 ai_big_subsys_path_cleanup(aip);
1429 }
1430
1431 return aip->targeted_subsys;
1432 }
1433
1434 /**
1435 * Called to init the data for single ai object.
1436 *
1437 * At this point, the ship and the object and the ai_info are are correctly
1438 * linked together. Ai_info[ai_index].shipnum is the only valid field
1439 * in ai_info.
1440 *
1441 * This is called right when the object is parsed, so you can't assume much
1442 * has been initialized. For example, wings, waypoints, goals are probably
1443 * not yet loaded. --MK, 10/8/96
1444 */
ai_object_init(object * obj,int ai_index)1445 void ai_object_init(object * obj, int ai_index)
1446 {
1447 ai_info *aip;
1448 Assert(ai_index >= 0 && ai_index < MAX_AI_INFO);
1449
1450 aip = &Ai_info[ai_index];
1451
1452 aip->type = 0; // 0 means not in use.
1453 aip->wing = -1; // Member of what wing? -1 means none.
1454 aip->ai_class = Ship_info[Ships[obj->instance].ship_info_index].ai_class;
1455 aip->behavior = AIM_NONE;
1456 aip->mode = aip->behavior;
1457 }
1458
1459 /**
1460 * If *aip is docked, set max acceleration to A->mass/(A->mass + B->mass) where A is *aip and B is dock object
1461 */
adjust_accel_for_docking(ai_info * aip)1462 void adjust_accel_for_docking(ai_info *aip)
1463 {
1464 object *objp = &Objects[Ships[aip->shipnum].objnum];
1465
1466 if (object_is_docked(objp))
1467 {
1468 float ratio = objp->phys_info.mass / dock_calc_total_docked_mass(objp);
1469
1470 // put cap on how much ship can slow down
1471 if ( (ratio < 0.8f) && !(The_mission.ai_profile->flags[AI::Profile_Flags::No_min_dock_speed_cap]) ) {
1472 ratio = 0.8f;
1473 }
1474
1475 // make sure we at least some velocity
1476 if (ratio < 0.1f) {
1477 ratio = 0.1f;
1478 }
1479
1480 if (AI_ci.forward > ratio) {
1481 AI_ci.forward = ratio;
1482 }
1483 }
1484 }
1485
accelerate_ship(ai_info * aip,float accel)1486 void accelerate_ship(ai_info *aip, float accel)
1487 {
1488 aip->prev_accel = accel;
1489 AI_ci.forward = accel;
1490 adjust_accel_for_docking(aip);
1491 }
1492
change_acceleration(ai_info * aip,float delta_accel)1493 void change_acceleration(ai_info *aip, float delta_accel)
1494 {
1495 float new_accel;
1496
1497 if (delta_accel < 0.0f) {
1498 if (aip->prev_accel > 0.0f)
1499 aip->prev_accel = 0.0f;
1500 } else if (aip->prev_accel < 0.0f)
1501 aip->prev_accel = 0.0f;
1502
1503 new_accel = aip->prev_accel + delta_accel * flFrametime;
1504
1505 if (new_accel > 1.0f)
1506 new_accel = 1.0f;
1507 else if (new_accel < -1.0f)
1508 new_accel = -1.0f;
1509
1510 aip->prev_accel = new_accel;
1511
1512 AI_ci.forward = new_accel;
1513 adjust_accel_for_docking(aip);
1514 }
1515
set_accel_for_target_speed(object * objp,float tspeed)1516 void set_accel_for_target_speed(object *objp, float tspeed)
1517 {
1518 float max_speed;
1519 ai_info *aip;
1520
1521 aip = &Ai_info[Ships[objp->instance].ai_index];
1522
1523 max_speed = Ships[objp->instance].current_max_speed;
1524
1525 if (max_speed > 0.0f) {
1526 AI_ci.forward = tspeed/max_speed;
1527 } else {
1528 AI_ci.forward = 0.0f;
1529 }
1530
1531 aip->prev_accel = AI_ci.forward;
1532
1533 adjust_accel_for_docking(aip);
1534 }
1535
1536 /**
1537 * Stuff perim_point with a point on the perimeter of the sphere defined by object *objp
1538 * on the vector from the center of *objp through the point *vp.
1539 */
project_point_to_perimeter(vec3d * perim_point,vec3d * pos,float radius,vec3d * vp)1540 void project_point_to_perimeter(vec3d *perim_point, vec3d *pos, float radius, vec3d *vp)
1541 {
1542 vec3d v1;
1543 float mag;
1544
1545 vm_vec_sub(&v1, vp, pos);
1546 mag = vm_vec_mag(&v1);
1547
1548 if (mag == 0.0f) {
1549 Warning(LOCATION, "projectable point is at center of sphere.");
1550 vm_vec_make(&v1, 0.0f, radius, 0.0f);
1551 } else {
1552 vm_vec_normalize(&v1);
1553 vm_vec_scale(&v1, 1.1f * radius + 10.0f);
1554 }
1555
1556 vm_vec_add2(&v1, pos);
1557 *perim_point = v1;
1558 }
1559
1560 /**
1561 * Stuff tan1 with tangent point on sphere.
1562 *
1563 * @param tan1 is point nearer to *p1
1564 * @param *p0 is point through which tangents pass.
1565 * @param *centerp is center of sphere.
1566 * @param *p1 is another point in space to define the plane in which tan1, tan2 reside.
1567 * @param radius is the radius of the sphere.
1568 *
1569 * Note, this is a very approximate function just for AI.
1570 * Note also: On 12/26/96, p1 is used to define the plane perpendicular to that which
1571 * contains the tangent point.
1572 */
get_tangent_point(vec3d * tan1,vec3d * p0,vec3d * centerp,vec3d * p1,float radius)1573 void get_tangent_point(vec3d *tan1, vec3d *p0, vec3d *centerp, vec3d *p1, float radius)
1574 {
1575 vec3d dest_vec, v2c, perp_vec, temp_vec, v2;
1576 float dist, ratio;
1577
1578 // Detect condition of point inside sphere.
1579 if (vm_vec_dist(p0, centerp) < radius)
1580 project_point_to_perimeter(tan1, centerp, radius, p0);
1581 else {
1582 vm_vec_normalized_dir(&v2c, centerp, p0);
1583
1584 // Compute perpendicular vector using p0, centerp, p1
1585 vm_vec_normal(&temp_vec, p0, centerp, p1);
1586 vm_vec_sub(&v2, centerp, p0);
1587 vm_vec_cross(&perp_vec, &temp_vec, &v2);
1588
1589 vm_vec_normalize(&perp_vec);
1590
1591 dist = vm_vec_dist_quick(p0, centerp);
1592 ratio = dist / radius;
1593
1594 if (ratio < 2.0f)
1595 vm_vec_scale_add(&dest_vec, &perp_vec, &v2c, ratio-1.0f);
1596 else
1597 vm_vec_scale_add(&dest_vec, &v2c, &perp_vec, (1.0f + 1.0f/ratio));
1598
1599 vm_vec_scale_add(tan1, p0, &dest_vec, dist + radius);
1600 }
1601 }
1602
1603 /**
1604 * Given an object and a point, turn towards the point, resulting in
1605 * approach behavior. Optional argument target_orient to attempt to match bank with
1606 */
turn_towards_point(object * objp,vec3d * point,vec3d * slide_vec,float bank_override,matrix * target_orient,int flags)1607 void turn_towards_point(object *objp, vec3d *point, vec3d *slide_vec, float bank_override, matrix* target_orient, int flags)
1608 {
1609 vec3d* rvec = nullptr;
1610 vec3d goal_rvec;
1611
1612 if (target_orient != nullptr) {
1613 rvec = &goal_rvec;
1614 vec3d goal_fvec;
1615
1616 vm_vec_normalized_dir(&goal_fvec,point, &objp->pos);
1617 vm_match_bank(rvec, &goal_fvec, target_orient);
1618 }
1619
1620 ai_turn_towards_vector(point, objp, slide_vec, nullptr, bank_override, flags, rvec);
1621 }
1622
1623 /**
1624 * Given an object and a point, turn away from the point, resulting in avoidance behavior.
1625 * Note: Turn away at full speed, not scaled down by skill level.
1626 */
turn_away_from_point(object * objp,vec3d * point,float bank_override)1627 void turn_away_from_point(object *objp, vec3d *point, float bank_override)
1628 {
1629 vec3d opposite_point;
1630
1631 vm_vec_sub(&opposite_point, &objp->pos, point);
1632 vm_vec_add2(&opposite_point, &objp->pos);
1633
1634 ai_turn_towards_vector(&opposite_point, objp, nullptr, nullptr, bank_override, AITTV_FAST);
1635 }
1636
1637
1638 // --------------------------------------------------------------------------
1639 // Given an object and a point, turn tangent to the point, resulting in
1640 // a circling behavior.
1641 // Make object *objp turn around the point *point with a radius of radius.
1642 // Note that this isn't the same as following a circle of radius radius with
1643 // center *point, but it should be adequate.
1644 // Note that if you want to circle an object without hitting it, you should use
1645 // about twice that object's radius for radius, else you'll certainly bump into it.
1646 // Return dot product to goal point.
turn_towards_tangent(object * objp,vec3d * point,float radius)1647 float turn_towards_tangent(object *objp, vec3d *point, float radius)
1648 {
1649 vec3d vec_to_point;
1650 vec3d goal_point;
1651 vec3d perp_point; // point radius away from *point on vector to objp->pos
1652 vec3d up_vec, perp_vec;
1653 vec3d v2g;
1654
1655 vm_vec_normalized_dir(&vec_to_point, point, &objp->pos);
1656 vm_vec_cross(&up_vec, &vec_to_point, &objp->orient.vec.fvec);
1657 vm_vec_cross(&perp_vec, &vec_to_point, &up_vec);
1658
1659 vm_vec_scale_add(&perp_point, point, &vec_to_point, -radius);
1660 if (vm_vec_dot(&objp->orient.vec.fvec, &perp_vec) > 0.0f) {
1661 vm_vec_scale_add(&goal_point, &perp_point, &perp_vec, radius);
1662 } else {
1663 vm_vec_scale_add(&goal_point, &perp_point, &perp_vec, -radius);
1664 }
1665
1666 turn_towards_point(objp, &goal_point, NULL, 0.0f);
1667
1668 vm_vec_normalized_dir(&v2g, &goal_point, &objp->pos);
1669 return vm_vec_dot(&objp->orient.vec.fvec, &v2g);
1670 }
1671
turn_toward_tangent_with_axis(object * objp,object * center_objp,float radius)1672 float turn_toward_tangent_with_axis(object *objp, object *center_objp, float radius)
1673 {
1674 vec3d r_vec, theta_vec;
1675 vec3d center_vec, vec_on_cylinder, sph_r_vec;
1676 float center_obj_z;
1677
1678 // find closest z of center objp
1679 vm_vec_sub(&sph_r_vec, &objp->pos, ¢er_objp->pos);
1680 center_obj_z = vm_vec_dot(&sph_r_vec, ¢er_objp->orient.vec.fvec);
1681
1682 // find pt on axis with closest z
1683 vm_vec_scale_add(¢er_vec, ¢er_objp->pos, ¢er_objp->orient.vec.fvec, center_obj_z);
1684
1685 // get r_vec
1686 vm_vec_sub(&r_vec, &objp->pos, ¢er_vec);
1687
1688 Assert( (vm_vec_dot(&r_vec, ¢er_objp->orient.vec.fvec) < 0.0001));
1689
1690 // get theta vec - perp to r_vec and z_vec
1691 vm_vec_cross(&theta_vec, ¢er_objp->orient.vec.fvec, &r_vec);
1692
1693 #ifndef NDEBUG
1694 float mag;
1695 mag = vm_vec_normalize(&theta_vec);
1696 Assert(mag > 0.9999 && mag < 1.0001);
1697 #endif
1698
1699 vec3d temp;
1700 vm_vec_cross(&temp, &r_vec, &theta_vec);
1701
1702 #ifndef NDEBUG
1703 float dot;
1704 dot = vm_vec_dot(&temp, ¢er_objp->orient.vec.fvec);
1705 Assert( dot >0.9999 && dot < 1.0001);
1706 #endif
1707
1708 // find pt on clylinder with closest z
1709 vm_vec_scale_add(&vec_on_cylinder, ¢er_vec, &r_vec, radius);
1710
1711 vec3d goal_pt, v2g;
1712 vm_vec_scale_add(&goal_pt, &vec_on_cylinder, &theta_vec, radius);
1713
1714 turn_towards_point(objp, &goal_pt, NULL, 0.0f);
1715
1716 vm_vec_normalized_dir(&v2g, &goal_pt, &objp->pos);
1717 return vm_vec_dot(&objp->orient.vec.fvec, &v2g);
1718 }
1719
1720 /**
1721 * Returns a point radius units away from *point that *objp should turn towards to orbit *point
1722 */
get_tangent_point(vec3d * goal_point,object * objp,vec3d * point,float radius)1723 void get_tangent_point(vec3d *goal_point, object *objp, vec3d *point, float radius)
1724 {
1725 vec3d vec_to_point;
1726 vec3d perp_point; // point radius away from *point on vector to objp->pos
1727 vec3d up_vec, perp_vec;
1728
1729 vm_vec_normalized_dir(&vec_to_point, point, &objp->pos);
1730 vm_vec_cross(&up_vec, &vec_to_point, &objp->orient.vec.fvec);
1731
1732 while (IS_VEC_NULL(&up_vec)) {
1733 vec3d rnd_vec;
1734
1735 vm_vec_rand_vec_quick(&rnd_vec);
1736 vm_vec_add2(&rnd_vec, &objp->orient.vec.fvec);
1737 vm_vec_cross(&up_vec, &vec_to_point, &rnd_vec);
1738 }
1739
1740 vm_vec_cross(&perp_vec, &vec_to_point, &up_vec);
1741 vm_vec_normalize(&perp_vec);
1742
1743 vm_vec_scale_add(&perp_point, point, &vec_to_point, -radius);
1744
1745 if (vm_vec_dot(&objp->orient.vec.fvec, &perp_vec) > 0.0f) {
1746 vm_vec_scale_add(goal_point, &perp_point, &perp_vec, radius);
1747 } else {
1748 vm_vec_scale_add(goal_point, &perp_point, &perp_vec, -radius);
1749 }
1750 }
1751
1752 int Player_attacking_enabled = 1;
1753
1754 /**
1755 * Determine whether an object is targetable within a nebula
1756 */
object_is_targetable(object * target,ship * viewer)1757 int object_is_targetable(object *target, ship *viewer)
1758 {
1759 // if target is ship, check if visible by team
1760 if (target->type == OBJ_SHIP)
1761 {
1762 if (ship_is_visible_by_team(target, viewer)) {
1763 return 1;
1764 }
1765
1766 // for AI partially targetable works as fully targetable, except for stealth ship
1767 if (Ships[target->instance].flags[Ship::Ship_Flags::Stealth]) {
1768 // if not team targetable, check if within frustum
1769 if ( ai_is_stealth_visible(&Objects[viewer->objnum], target) == STEALTH_IN_FRUSTUM ) {
1770 return 1;
1771 } else {
1772 return 0;
1773 }
1774 }
1775 }
1776
1777 // if not fully targetable by team, check awacs level with viewer
1778 // allow targeting even if only only partially targetable to player
1779 float radar_return = awacs_get_level(target, viewer);
1780 if ( radar_return > 0.4 ) {
1781 return 1;
1782 } else {
1783 return 0;
1784 }
1785 }
1786
1787 /**
1788 * Return number of enemies attacking object objnum
1789 */
num_enemies_attacking(int objnum)1790 int num_enemies_attacking(int objnum)
1791 {
1792 object *objp;
1793 ship *sp;
1794 ship_subsys *ssp;
1795 ship_obj *so;
1796 int count;
1797
1798 count = 0;
1799
1800 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1801 objp = &Objects[so->objnum];
1802 Assert(objp->instance != -1);
1803 sp = &Ships[objp->instance];
1804
1805 if (Ai_info[sp->ai_index].target_objnum == objnum)
1806 count++;
1807
1808 // consider turrets that may be attacking objnum (but only turrets on SIF_BIG_SHIP ships)
1809 if ( Ship_info[sp->ship_info_index].is_big_ship() ) {
1810
1811 // loop through all the subsystems, check if turret has objnum as a target
1812 ssp = GET_FIRST(&sp->subsys_list);
1813 while ( ssp != END_OF_LIST( &sp->subsys_list ) ) {
1814
1815 if ( ssp->system_info->type == SUBSYSTEM_TURRET ) {
1816 if ( (ssp->turret_enemy_objnum == objnum) && (ssp->current_hits > 0) ) {
1817 count++;
1818 }
1819 }
1820 ssp = GET_NEXT( ssp );
1821 } // end while
1822 }
1823 }
1824
1825 return count;
1826 }
1827
1828 /**
1829 * Scan all the ships in *objp's wing. Return the lowest maximum speed of a ship in the wing.
1830 *
1831 * Current maximum speed (based on energy settings) is shipp->current_max_speed
1832 */
get_wing_lowest_max_speed(object * objp)1833 float get_wing_lowest_max_speed(object *objp)
1834 {
1835 ship *shipp;
1836 ai_info *aip;
1837 float lowest_max_speed;
1838 int wingnum;
1839 object *o;
1840 ship_obj *so;
1841
1842 Assert(objp->type == OBJ_SHIP);
1843 Assert((objp->instance >= 0) && (objp->instance < MAX_OBJECTS));
1844 shipp = &Ships[objp->instance];
1845 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
1846 aip = &Ai_info[shipp->ai_index];
1847
1848 wingnum = aip->wing;
1849
1850 lowest_max_speed = shipp->current_max_speed;
1851
1852 if ( wingnum == -1 )
1853 return lowest_max_speed;
1854
1855 Assert(wingnum >= 0);
1856
1857 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1858 o = &Objects[so->objnum];
1859 ship *oshipp = &Ships[o->instance];
1860 ai_info *oaip = &Ai_info[oshipp->ai_index];
1861
1862 if ((oaip->mode == AIM_WAYPOINTS) && (oaip->wing == wingnum)) {
1863 // Note: If a ship in the wing has a super low max speed, probably its engines are disabled. So, fly along and
1864 // ignore the poor guy.
1865 float cur_max = oshipp->current_max_speed;
1866
1867 if (object_is_docked(o)) {
1868 cur_max *= o->phys_info.mass / dock_calc_total_docked_mass(o);
1869 }
1870
1871 if ((oshipp->current_max_speed > 5.0f) && (cur_max < lowest_max_speed)) {
1872 lowest_max_speed = cur_max;
1873 }
1874 }
1875 }
1876
1877 return lowest_max_speed;
1878 }
1879
1880 /**
1881 * Scan all the ships in *objp's wing. Return the lowest average speed with afterburner of a ship in the wing.
1882 */
get_wing_lowest_av_ab_speed(object * objp)1883 float get_wing_lowest_av_ab_speed(object *objp)
1884 {
1885 ship *shipp;
1886 ai_info *aip;
1887 float lowest_max_av_ab_speed;
1888 float recharge_scale;
1889 int wingnum;
1890 object *o;
1891 ship_obj *so;
1892 ship_info *sip;
1893
1894 Assert(objp->type == OBJ_SHIP);
1895 Assert((objp->instance >= 0) && (objp->instance < MAX_OBJECTS));
1896 shipp = &Ships[objp->instance];
1897 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
1898 aip = &Ai_info[shipp->ai_index];
1899 sip = &Ship_info[shipp->ship_info_index];
1900
1901 wingnum = aip->wing;
1902
1903 if (((shipp->flags[Ship::Ship_Flags::Afterburner_locked]) || !(sip->flags[Ship::Info_Flags::Afterburner])) || (shipp->current_max_speed < 5.0f) || (objp->phys_info.afterburner_max_vel.xyz.z <= shipp->current_max_speed) || !(aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use])) {
1904 lowest_max_av_ab_speed = shipp->current_max_speed;
1905 }
1906 else
1907 {
1908 recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level];
1909 recharge_scale = sip->afterburner_recover_rate * recharge_scale / (sip->afterburner_burn_rate + sip->afterburner_recover_rate * recharge_scale);
1910 lowest_max_av_ab_speed = recharge_scale * (objp->phys_info.afterburner_max_vel.xyz.z - shipp->current_max_speed) + shipp->current_max_speed;
1911 }
1912
1913
1914 if ( wingnum == -1 )
1915 return lowest_max_av_ab_speed;
1916
1917 Assert(wingnum >= 0);
1918
1919 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1920 o = &Objects[so->objnum];
1921 ship *oshipp = &Ships[o->instance];
1922 ai_info *oaip = &Ai_info[oshipp->ai_index];
1923 ship_info *osip = &Ship_info[oshipp->ship_info_index];
1924
1925 if ((oaip->mode == AIM_WAYPOINTS) && (oaip->wing == wingnum) && (oaip->ai_flags[AI::AI_Flags::Formation_object, AI::AI_Flags::Formation_wing])) {
1926
1927 float cur_max;
1928 if ((oshipp->flags[Ship::Ship_Flags::Afterburner_locked]) || !(osip->flags[Ship::Info_Flags::Afterburner]) || (o->phys_info.afterburner_max_vel.xyz.z <= oshipp->current_max_speed) || !(oaip->ai_flags[AI::AI_Flags::Free_afterburner_use] || oaip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use])) {
1929 cur_max = oshipp->current_max_speed;
1930 }
1931 else
1932 {
1933 recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level];
1934 recharge_scale = osip->afterburner_recover_rate * recharge_scale / (osip->afterburner_burn_rate + osip->afterburner_recover_rate * recharge_scale);
1935 cur_max = recharge_scale * (o->phys_info.afterburner_max_vel.xyz.z - oshipp->current_max_speed) + oshipp->current_max_speed;
1936 }
1937
1938 if (object_is_docked(o)) {
1939 cur_max *= o->phys_info.mass / dock_calc_total_docked_mass(o);
1940 }
1941 // Note: If a ship in the wing has a super low max speed, probably its engines are disabled. So, fly along and
1942 // ignore the poor guy.
1943 if ((oshipp->current_max_speed > 5.0f) && (cur_max < lowest_max_av_ab_speed)) {
1944 lowest_max_av_ab_speed = cur_max;
1945 }
1946 }
1947 }
1948
1949 return lowest_max_av_ab_speed;
1950 }
1951
1952 /**
1953 * Determine if object objnum is supposed to be ignored by object with ai_info *aip.
1954 * @return TRUE if objnum is aip->ignore_objnum (and signatures match) or objnum is in ignore wing
1955 * @return FALSE otherwise
1956 */
is_ignore_object_sub(int * ignore_objnum,int * ignore_signature,int objnum)1957 int is_ignore_object_sub(int *ignore_objnum, int *ignore_signature, int objnum)
1958 {
1959 // Should never happen. I thought I removed this behavior! -- MK, 5/17/98
1960 Assertion(((*ignore_objnum <= UNUSED_OBJNUM) || (*ignore_objnum >= 0)), "Unexpected value for ignore_objnum %d. This is a coder error, please report.", *ignore_objnum);
1961 // whether this is an invalid OBJNUM or the official UNUSED_OBJNUM, we should just return here.
1962 // As a note, if it was set to UNUSED_OBJNUM, it would be trying to ignore a wing and not an object. (at least according to previously written comments)
1963 if (*ignore_objnum < 0) {
1964 return 0;
1965 }
1966
1967 // see if this object became invalid
1968 if (Objects[*ignore_objnum].signature != *ignore_signature)
1969 {
1970 // reset
1971 *ignore_objnum = UNUSED_OBJNUM;
1972 }
1973 // objects and signatures match
1974 else if (*ignore_objnum == objnum)
1975 {
1976 // found it
1977 return 1;
1978 }
1979
1980 return 0;
1981 }
1982
1983 // Goober5000
find_ignore_new_object_index(ai_info * aip,int objnum)1984 int find_ignore_new_object_index(ai_info *aip, int objnum)
1985 {
1986 int i;
1987
1988 for (i = 0; i < MAX_IGNORE_NEW_OBJECTS; i++)
1989 {
1990 if (is_ignore_object_sub(&aip->ignore_new_objnums[i], &aip->ignore_new_signatures[i], objnum))
1991 return i;
1992 }
1993
1994 return -1;
1995 }
1996
1997 // Goober5000
is_ignore_object(ai_info * aip,int objnum,int just_the_original=0)1998 int is_ignore_object(ai_info *aip, int objnum, int just_the_original = 0)
1999 {
2000 // check original (retail) ignore
2001 if (is_ignore_object_sub(&aip->ignore_objnum, &aip->ignore_signature, objnum))
2002 return 1;
2003
2004 // check new ignore
2005 if (!just_the_original)
2006 {
2007 if (find_ignore_new_object_index(aip, objnum) >= 0)
2008 return 1;
2009 }
2010
2011 return 0;
2012 }
2013
2014
2015
2016
2017 typedef struct eval_nearest_objnum {
2018 int objnum;
2019 object *trial_objp;
2020 int enemy_team_mask;
2021 int enemy_ship_info_index;
2022 int enemy_wing;
2023 float range;
2024 int max_attackers;
2025 int nearest_objnum;
2026 float nearest_dist;
2027 int check_danger_weapon_objnum;
2028 } eval_nearest_objnum;
2029
2030
evaluate_object_as_nearest_objnum(eval_nearest_objnum * eno)2031 void evaluate_object_as_nearest_objnum(eval_nearest_objnum *eno)
2032 {
2033 ai_info *aip;
2034 ship_subsys *attacking_subsystem;
2035 ship *shipp = &Ships[eno->trial_objp->instance];
2036
2037 aip = &Ai_info[Ships[Objects[eno->objnum].instance].ai_index];
2038
2039 attacking_subsystem = aip->targeted_subsys;
2040
2041 if ((attacking_subsystem != NULL) || !(eno->trial_objp->flags[Object::Object_Flags::Protected])) {
2042 if ( OBJ_INDEX(eno->trial_objp) != eno->objnum ) {
2043 #ifndef NDEBUG
2044 if (!Player_attacking_enabled && (eno->trial_objp == Player_obj))
2045 return;
2046 #endif
2047 // If only supposed to attack ship in a specific wing, don't attack other ships.
2048 if ((eno->enemy_wing != -1) && (shipp->wingnum != eno->enemy_wing))
2049 return;
2050
2051 // If only supposed to attack ships of a certain ship class, don't attack other ships.
2052 if ((eno->enemy_ship_info_index >= 0) && (shipp->ship_info_index != eno->enemy_ship_info_index))
2053 return;
2054
2055 // Don't keep firing at a ship that is in its death throes.
2056 if (shipp->flags[Ship::Ship_Flags::Dying])
2057 return;
2058
2059 if (is_ignore_object(aip, OBJ_INDEX(eno->trial_objp)))
2060 return;
2061
2062 if (eno->trial_objp->flags[Object::Object_Flags::Protected])
2063 return;
2064
2065 if (shipp->is_arriving())
2066 return;
2067
2068 ship_info *sip = &Ship_info[shipp->ship_info_index];
2069
2070 if (sip->flags[Ship::Info_Flags::No_ship_type] || sip->flags[Ship::Info_Flags::Navbuoy])
2071 return;
2072
2073 if (iff_matches_mask(shipp->team, eno->enemy_team_mask)) {
2074 float dist;
2075 int num_attacking;
2076
2077 // Allow targeting of stealth in nebula by his firing at me
2078 // This is done for a specific ship, not generally.
2079 if ( !eno->check_danger_weapon_objnum ) {
2080 // check if can be targeted if inside nebula
2081 if ( !object_is_targetable(eno->trial_objp, &Ships[Objects[eno->objnum].instance]) ) {
2082 // check if stealth ship is visible, but not "targetable"
2083 if ( !((shipp->flags[Ship::Ship_Flags::Stealth]) && ai_is_stealth_visible(&Objects[eno->objnum], eno->trial_objp)) ) {
2084 return;
2085 }
2086 }
2087 }
2088
2089 // if objnum is BIG or HUGE, find distance to bbox
2090 if (sip->is_big_or_huge()) {
2091 vec3d box_pt;
2092 // check if inside bbox
2093 int inside = get_nearest_bbox_point(eno->trial_objp, &Objects[eno->objnum].pos, &box_pt);
2094 if (inside) {
2095 dist = 10.0f;
2096 // on the box
2097 } else {
2098 dist = vm_vec_dist_quick(&Objects[eno->objnum].pos, &box_pt);
2099 }
2100 } else {
2101 dist = vm_vec_dist_quick(&Objects[eno->objnum].pos, &eno->trial_objp->pos);
2102 }
2103
2104 // Make it more likely that fighters (or bombers) will be picked as an enemy by scaling up distance for other types.
2105 if (Ship_info[shipp->ship_info_index].is_fighter_bomber()) {
2106 dist = dist * 0.5f;
2107 }
2108
2109 num_attacking = num_enemies_attacking(OBJ_INDEX(eno->trial_objp));
2110
2111 if (!sip->is_big_or_huge() && num_attacking < eno->max_attackers) {
2112 dist *= (float)(num_attacking + 2) / 2.0f; // prevents lots of ships from attacking same target
2113 }
2114
2115 if ((sip->is_big_or_huge()) || (num_attacking < eno->max_attackers)) {
2116
2117 if (eno->trial_objp->flags[Object::Object_Flags::Player_ship]){
2118 dist *= 1.0f + (NUM_SKILL_LEVELS - Game_skill_level - 1)/NUM_SKILL_LEVELS; // Favor attacking non-players based on skill level.
2119 }
2120
2121 if (dist < eno->nearest_dist) {
2122 eno->nearest_dist = dist;
2123 eno->nearest_objnum = OBJ_INDEX(eno->trial_objp);
2124 }
2125 }
2126 }
2127 }
2128 }
2129
2130 }
2131
2132
2133 /**
2134 * Given an object and an enemy team, return the index of the nearest enemy object.
2135 * Unless aip->targeted_subsys != NULL, don't allow to attack objects with OF_PROTECTED bit set.
2136 *
2137 * @param objnum Object number
2138 * @param enemy_team_mask Mask to apply to enemy team
2139 * @param enemy_wing Enemy wing chosen
2140 * @param range Ship must be within range "range".
2141 * @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
2142 * @param ship_info_index If >=0, the enemy object must be of the specified ship class
2143 */
get_nearest_objnum(int objnum,int enemy_team_mask,int enemy_wing,float range,int max_attackers,int ship_info_index)2144 int get_nearest_objnum(int objnum, int enemy_team_mask, int enemy_wing, float range, int max_attackers, int ship_info_index)
2145 {
2146 object *danger_weapon_objp;
2147 ai_info *aip;
2148 ship_obj *so;
2149
2150 // initialize eno struct
2151 eval_nearest_objnum eno;
2152 eno.enemy_team_mask = enemy_team_mask;
2153 eno.enemy_ship_info_index = ship_info_index;
2154 eno.enemy_wing = enemy_wing;
2155 eno.max_attackers = max_attackers;
2156 eno.objnum = objnum;
2157 eno.range = range;
2158 eno.nearest_dist = range;
2159 eno.nearest_objnum = -1;
2160 eno.check_danger_weapon_objnum = 0;
2161
2162 // go through the list of all ships and evaluate as potential targets
2163 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
2164 eno.trial_objp = &Objects[so->objnum];
2165 evaluate_object_as_nearest_objnum(&eno);
2166 }
2167
2168 // check if danger_weapon_objnum has will show a stealth ship
2169 aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
2170 if (aip->danger_weapon_objnum >= 0) {
2171 danger_weapon_objp = &Objects[aip->danger_weapon_objnum];
2172 // validate weapon
2173 if (danger_weapon_objp->signature == aip->danger_weapon_signature) {
2174 Assert(danger_weapon_objp->type == OBJ_WEAPON);
2175 // check if parent is a ship
2176 if (danger_weapon_objp->parent >= 0) {
2177 if ( is_object_stealth_ship(&Objects[danger_weapon_objp->parent]) ) {
2178 // check if stealthy
2179 if ( ai_is_stealth_visible(&Objects[objnum], &Objects[danger_weapon_objp->parent]) != STEALTH_FULLY_TARGETABLE ) {
2180 // check if weapon is laser
2181 if (Weapon_info[Weapons[danger_weapon_objp->instance].weapon_info_index].subtype == WP_LASER) {
2182 // check stealth ship by its laser fire
2183 eno.check_danger_weapon_objnum = 1;
2184 eno.trial_objp = &Objects[danger_weapon_objp->parent];
2185 evaluate_object_as_nearest_objnum(&eno);
2186 }
2187 }
2188 }
2189 }
2190 }
2191 }
2192
2193 // If only looking for target in certain wing and couldn't find anything in
2194 // that wing, look for any object.
2195 if ((eno.nearest_objnum == -1) && (enemy_wing != -1)) {
2196 return get_nearest_objnum(objnum, enemy_team_mask, -1, range, max_attackers, ship_info_index);
2197 }
2198
2199 return eno.nearest_objnum;
2200 }
2201
2202 /**
2203 * Given an object and an enemy team, return the index of the nearest enemy object.
2204 *
2205 * Unlike find_enemy or find_nearest_objnum, this doesn't care about things like the protected flag or number of enemies attacking.
2206 * It is used to find the nearest enemy to determine things like whether to rearm.
2207 */
find_nearby_threat(int objnum,int enemy_team_mask,float range,int * count)2208 int find_nearby_threat(int objnum, int enemy_team_mask, float range, int *count)
2209 {
2210 int nearest_objnum;
2211 float nearest_dist;
2212 object *objp;
2213 ship_obj *so;
2214
2215 nearest_objnum = -1;
2216 nearest_dist = range;
2217
2218 *count = 0;
2219
2220 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
2221 objp = &Objects[so->objnum];
2222
2223 if ( OBJ_INDEX(objp) != objnum ) {
2224 if (Ships[objp->instance].flags[Ship::Ship_Flags::Dying])
2225 continue;
2226
2227 if (Ship_info[Ships[objp->instance].ship_info_index].flags[Ship::Info_Flags::No_ship_type] || Ship_info[Ships[objp->instance].ship_info_index].flags[Ship::Info_Flags::Navbuoy])
2228 continue;
2229
2230 if (iff_matches_mask(Ships[objp->instance].team, enemy_team_mask)) {
2231 float dist;
2232
2233 dist = vm_vec_dist_quick(&Objects[objnum].pos, &objp->pos) - objp->radius*0.75f;
2234
2235 if (dist < range) {
2236 (*count)++;
2237
2238 if (dist < nearest_dist) {
2239 nearest_dist = dist;
2240 nearest_objnum = OBJ_INDEX(objp);
2241 }
2242 }
2243 }
2244 }
2245 }
2246
2247 return nearest_objnum;
2248 }
2249
2250 /**
2251 * Number of live turrets with target_objnum
2252 */
num_turrets_attacking(object * turret_parent,int target_objnum)2253 int num_turrets_attacking(object *turret_parent, int target_objnum)
2254 {
2255 ship_subsys *ss;
2256 ship *shipp;
2257 int count = 0;
2258 shipp = &Ships[turret_parent->instance];
2259
2260 Assert(turret_parent->type == OBJ_SHIP);
2261
2262 for (ss=GET_FIRST(&shipp->subsys_list); ss!=END_OF_LIST(&shipp->subsys_list); ss=GET_NEXT(ss)) {
2263 // check if subsys is alive
2264 if (ss->current_hits <= 0.0f) {
2265 continue;
2266 }
2267
2268 // check if it's a turret
2269 if (ss->system_info->type != SUBSYSTEM_TURRET) {
2270 continue;
2271 }
2272
2273 // if the turret is locked
2274 if(ss->weapons.flags[Ship::Weapon_Flags::Turret_Lock]){
2275 continue;
2276 }
2277
2278 // check if turret is targeting target_objnum
2279 if (ss->turret_enemy_objnum == target_objnum) {
2280 count++;
2281 }
2282 }
2283
2284 return count;
2285 }
2286
2287 /**
2288 * Return timestamp until a ship can find an enemy.
2289 */
get_enemy_timestamp()2290 int get_enemy_timestamp()
2291 {
2292 return (NUM_SKILL_LEVELS - Game_skill_level) * Random::next(500, 999);
2293 }
2294
2295 /**
2296 * Return objnum if enemy found, else return -1;
2297 *
2298 * @param objnum Object number
2299 * @param range Range within which to look
2300 * @param max_attackers Don't attack a ship that already has at least max_attackers attacking it.
2301 */
find_enemy(int objnum,float range,int max_attackers,int ship_info_index)2302 int find_enemy(int objnum, float range, int max_attackers, int ship_info_index)
2303 {
2304 int enemy_team_mask;
2305
2306 if (objnum < 0)
2307 return -1;
2308
2309 enemy_team_mask = iff_get_attackee_mask(obj_team(&Objects[objnum]));
2310
2311 // if target_objnum != -1, use that as goal.
2312 ai_info *aip = &Ai_info[Ships[Objects[objnum].instance].ai_index];
2313 if (timestamp_elapsed(aip->choose_enemy_timestamp)) {
2314 aip->choose_enemy_timestamp = timestamp(get_enemy_timestamp());
2315 if (aip->target_objnum != -1) {
2316 int target_objnum = aip->target_objnum;
2317
2318 // DKA don't undo object as target in nebula missions.
2319 // This could cause attack on ship on fringe on nebula to stop if attackee moves our of nebula range. (BAD)
2320 if ( Objects[target_objnum].signature == aip->target_signature ) {
2321 if (iff_matches_mask(Ships[Objects[target_objnum].instance].team, enemy_team_mask)) {
2322 if (ship_info_index < 0 || ship_info_index == Ships[Objects[target_objnum].instance].ship_info_index) {
2323 if (!(Objects[target_objnum].flags[Object::Object_Flags::Protected])) {
2324 return target_objnum;
2325 }
2326 }
2327 }
2328 } else {
2329 aip->target_objnum = -1;
2330 aip->target_signature = -1;
2331 }
2332 }
2333
2334 return get_nearest_objnum(objnum, enemy_team_mask, aip->enemy_wing, range, max_attackers, ship_info_index);
2335
2336 } else {
2337 aip->target_objnum = -1;
2338 aip->target_signature = -1;
2339 return -1;
2340 }
2341 }
2342
2343 /**
2344 * If issued an order to a ship that's awaiting repair, abort that process.
2345 * However, do not abort process for an object that is currently being repaired -- let it finish.
2346 */
ai_set_goal_abort_support_call(object * objp,ai_info * aip)2347 void ai_set_goal_abort_support_call(object *objp, ai_info *aip)
2348 {
2349 if (aip->ai_flags[AI::AI_Flags::Awaiting_repair]) {
2350 object *repair_obj;
2351
2352 if (aip->support_ship_objnum == -1) {
2353 repair_obj = nullptr;
2354 } else {
2355 Assertion(aip->support_ship_objnum >= 0, "%s has a nonsense support_ship_objnum of %d, please report!", Ships[aip->shipnum].ship_name, aip->support_ship_objnum);
2356 repair_obj = &Objects[aip->support_ship_objnum];
2357 }
2358 ai_do_objects_repairing_stuff( objp, repair_obj, REPAIR_INFO_ABORT );
2359 }
2360 aip->next_rearm_request_timestamp = timestamp(NEXT_REARM_TIMESTAMP); // Might request again after 30 seconds.
2361 }
2362
force_avoid_player_check(object * objp,ai_info * aip)2363 void force_avoid_player_check(object *objp, ai_info *aip)
2364 {
2365 if (Ships[objp->instance].team == Player_ship->team){
2366 aip->avoid_check_timestamp = timestamp(0); // Force a check for collision next frame.
2367 }
2368 }
2369
2370 /**
2371 * Set *attacked as object to attack for object *attacker
2372 *
2373 * If attacked == NULL, then attack any enemy object.
2374 * Attack point *rel_pos on object. This is for supporting attacking subsystems.
2375 */
ai_attack_object(object * attacker,object * attacked,int ship_info_index)2376 void ai_attack_object(object* attacker, object* attacked, int ship_info_index)
2377 {
2378 int temp;
2379 ai_info* aip;
2380
2381 Assert(attacker != nullptr);
2382 Assert(attacker->instance >= 0);
2383 Assert(Ships[attacker->instance].ai_index != -1);
2384 // Bogus! Who tried to get me to attack myself!
2385 if (attacker == attacked) {
2386 Warning(LOCATION, "%s was told to attack itself! This may be an error in the mission file. If that checks out, please report to a coder!", Ships[attacker->instance].ship_name);
2387 return;
2388 }
2389
2390 if (attacker == nullptr || attacker->instance == -1) {
2391 return;
2392 }
2393
2394 aip = &Ai_info[Ships[attacker->instance].ai_index];
2395 force_avoid_player_check(attacker, aip);
2396
2397 aip->ok_to_target_timestamp = timestamp(0); // Guarantee we can target.
2398
2399 // Only set to chase if a fighter or bomber, otherwise just return.
2400 if (!(Ship_info[Ships[attacker->instance].ship_info_index].is_small_ship()) && (attacked != NULL)) {
2401 nprintf(("AI", "AI ship %s is large ship ordered to attack %s\n", Ships[attacker->instance].ship_name, Ships[attacked->instance].ship_name));
2402 }
2403
2404 // This is how "engage enemy" gets processed
2405 if (attacked == nullptr) {
2406 aip->choose_enemy_timestamp = timestamp(0);
2407 // nebula safe
2408 set_target_objnum(aip, find_enemy(OBJ_INDEX(attacker), 99999.9f, 4, ship_info_index));
2409 } else {
2410 // check if we can see attacked in nebula
2411 if (aip->target_objnum != OBJ_INDEX(attacked)) {
2412 aip->aspect_locked_time = 0.0f;
2413 }
2414 set_target_objnum(aip, OBJ_INDEX(attacked));
2415 }
2416
2417 // don't abort a support call if you're disabled!
2418 if (ship_get_subsystem_strength(&Ships[attacker->instance], SUBSYSTEM_ENGINE) > 0.0f)
2419 ai_set_goal_abort_support_call(attacker, aip);
2420
2421 aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME); // No dynamic targeting for 7 seconds.
2422
2423 // Goober5000
2424 temp = find_ignore_new_object_index(aip, aip->target_objnum);
2425 if (temp >= 0)
2426 {
2427 aip->ignore_new_objnums[temp] = UNUSED_OBJNUM;
2428 }
2429 else if (is_ignore_object(aip, aip->target_objnum, 1))
2430 {
2431 aip->ignore_objnum = UNUSED_OBJNUM;
2432 }
2433
2434 aip->mode = AIM_CHASE;
2435 aip->submode = SM_ATTACK; // AL 12-15-97: need to set submode? I got an assert() where submode was bogus
2436 aip->submode_start_time = Missiontime; // for AIM_CHASE... it may have been not set correctly here
2437
2438 set_targeted_subsys(aip, nullptr , -1);
2439 if (aip->target_objnum != -1) {
2440 Objects[aip->target_objnum].flags.remove(Object::Object_Flags::Protected); // If ship had been protected, unprotect it.
2441 }
2442 }
2443
2444 /**
2445 * Set *attacked as object to attack for object *attacker
2446 * Attack point *rel_pos on object. This is for supporting attacking subsystems.
2447 */
ai_attack_wing(object * attacker,int wingnum)2448 void ai_attack_wing(object *attacker, int wingnum)
2449 {
2450 ai_info *aip;
2451
2452 Assert(attacker != NULL);
2453 Assert(attacker->instance != -1);
2454 Assert(Ships[attacker->instance].ai_index != -1);
2455
2456 aip = &Ai_info[Ships[attacker->instance].ai_index];
2457
2458 aip->enemy_wing = wingnum;
2459 aip->mode = AIM_CHASE;
2460 aip->submode = SM_ATTACK; // AL 12-15-97: need to set submode? I got an assert() where submode was bogus
2461 aip->submode_start_time = Missiontime; // for AIM_CHASE... it may have been not set correctly here
2462
2463 aip->ok_to_target_timestamp = timestamp(0); // Guarantee we can target.
2464
2465 int count = Wings[wingnum].current_count;
2466 if (count > 0) {
2467 int index;
2468
2469 index = (int) (frand() * count);
2470
2471 if (index >= count)
2472 index = 0;
2473
2474 set_target_objnum(aip, Ships[Wings[wingnum].ship_index[index]].objnum);
2475
2476 ai_set_goal_abort_support_call(attacker, aip);
2477 aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME); // No dynamic targeting for 7 seconds.
2478 }
2479 }
2480
2481 /**
2482 * Set *evaded as object for *evader to evade.
2483 */
ai_evade_object(object * evader,object * evaded)2484 void ai_evade_object(object *evader, object *evaded)
2485 {
2486 ai_info *aip;
2487
2488 Assert(evader != NULL);
2489 Assert(evaded != NULL);
2490 Assert(evader->instance != -1);
2491 Assert(Ships[evader->instance].ai_index != -1);
2492
2493 if (evaded == evader) {
2494 Int3(); // Bogus! Who tried to get me to evade myself! Trace out and fix!
2495 return;
2496 }
2497
2498 aip = &Ai_info[Ships[evader->instance].ai_index];
2499
2500 set_target_objnum(aip, OBJ_INDEX(evaded));
2501 aip->mode = AIM_EVADE;
2502
2503 }
2504
2505 /**
2506 * Returns total number of ignored objects.
2507 *
2508 * @param aip AI info
2509 * @param force Means we forget the oldest object
2510 */
compact_ignore_new_objects(ai_info * aip,int force=0)2511 int compact_ignore_new_objects(ai_info *aip, int force = 0)
2512 {
2513 if (force)
2514 aip->ignore_new_objnums[0] = UNUSED_OBJNUM;
2515
2516 for (int current_index = 0; current_index < MAX_IGNORE_NEW_OBJECTS; current_index++)
2517 {
2518 int next_occupied_index = -1;
2519
2520 // skip occupied slots
2521 if (aip->ignore_new_objnums[current_index] != UNUSED_OBJNUM)
2522 {
2523 // prune invalid objects
2524 if (Objects[aip->ignore_new_objnums[current_index]].signature != aip->ignore_new_signatures[current_index])
2525 aip->ignore_new_objnums[current_index] = UNUSED_OBJNUM;
2526 else
2527 continue;
2528 }
2529
2530 // find an index to move downward
2531 for (int i = current_index + 1; i < MAX_IGNORE_NEW_OBJECTS; i++)
2532 {
2533 // skip empty slots
2534 if (aip->ignore_new_objnums[i] == UNUSED_OBJNUM)
2535 continue;
2536
2537 // found one
2538 next_occupied_index = i;
2539 break;
2540 }
2541
2542 // are all higher slots empty?
2543 if (next_occupied_index < 0)
2544 return current_index;
2545
2546 // move the occupied slot down to this one
2547 aip->ignore_new_objnums[current_index] = aip->ignore_new_objnums[next_occupied_index];
2548 aip->ignore_new_signatures[current_index] = aip->ignore_new_signatures[next_occupied_index];
2549
2550 // empty the occupied slot
2551 aip->ignore_new_objnums[next_occupied_index] = UNUSED_OBJNUM;
2552 aip->ignore_new_signatures[next_occupied_index] = -1;
2553 }
2554
2555 // all slots are occupied
2556 return MAX_IGNORE_NEW_OBJECTS;
2557 }
2558
2559 /**
2560 * Ignore some object without changing mode.
2561 */
ai_ignore_object(object * ignorer,object * ignored,int ignore_new)2562 void ai_ignore_object(object *ignorer, object *ignored, int ignore_new)
2563 {
2564 ai_info *aip;
2565
2566 Assert(ignorer != NULL);
2567 Assert(ignored != NULL);
2568 Assert(ignorer->instance != -1);
2569 Assert(Ships[ignorer->instance].ai_index != -1);
2570 Assert(ignorer != ignored);
2571
2572 aip = &Ai_info[Ships[ignorer->instance].ai_index];
2573
2574 // Goober5000 - new behavior
2575 if (ignore_new)
2576 {
2577 int num_objects;
2578
2579 // compact the array
2580 num_objects = compact_ignore_new_objects(aip);
2581
2582 // make sure we're not adding a duplicate
2583 if (find_ignore_new_object_index(aip, OBJ_INDEX(ignored)) >= 0)
2584 return;
2585
2586 // if we can't add a new one; "forget" one
2587 if (num_objects >= MAX_IGNORE_NEW_OBJECTS)
2588 num_objects = compact_ignore_new_objects(aip, 1);
2589
2590 // add it
2591 aip->ignore_new_objnums[num_objects] = OBJ_INDEX(ignored);
2592 aip->ignore_new_signatures[num_objects] = ignored->signature;
2593 }
2594 // retail behavior
2595 else
2596 {
2597 aip->ignore_objnum = OBJ_INDEX(ignored);
2598 aip->ignore_signature = ignored->signature;
2599 aip->ai_flags.remove(AI::AI_Flags::Temporary_ignore);
2600 ignored->flags.set(Object::Object_Flags::Protected); // set protected bit of ignored ship.
2601 }
2602 }
2603
2604 /**
2605 * Ignore some object without changing mode.
2606 */
ai_ignore_wing(object * ignorer,int wingnum)2607 void ai_ignore_wing(object *ignorer, int wingnum)
2608 {
2609 ai_info *aip;
2610
2611 Assert(ignorer != NULL);
2612 Assert(ignorer->instance != -1);
2613 Assert(Ships[ignorer->instance].ai_index != -1);
2614 Assert((wingnum >= 0) && (wingnum < MAX_WINGS));
2615
2616 aip = &Ai_info[Ships[ignorer->instance].ai_index];
2617
2618 aip->ignore_objnum = -(wingnum +1);
2619 aip->ai_flags.remove(AI::AI_Flags::Temporary_ignore);
2620 }
2621
2622
2623 /**
2624 * Add a path point in the global buffer Path_points.
2625 *
2626 * If modify_index == -1, then create a new point.
2627 * If a new point is created (ie, modify_index == -1), then Ppfp is updated.
2628 *
2629 * @param pos Position in vector space
2630 * @param path_num Path numbers
2631 * @param path_index Index into path
2632 * @param modify_index Index in Path_points at which to store path point.
2633 */
add_path_point(vec3d * pos,int path_num,int path_index,int modify_index)2634 void add_path_point(vec3d *pos, int path_num, int path_index, int modify_index)
2635 {
2636 pnode *pnp;
2637
2638 if (modify_index == -1) {
2639 Assert(Ppfp-Path_points < MAX_PATH_POINTS-1);
2640 pnp = Ppfp;
2641 Ppfp++;
2642 } else {
2643 Assert((modify_index >= 0) && (modify_index < MAX_PATH_POINTS-1));
2644 pnp = &Path_points[modify_index];
2645 }
2646
2647 pnp->pos = *pos;
2648 pnp->path_num = path_num;
2649 pnp->path_index = path_index;
2650 }
2651
2652 /**
2653 * Given two points on a sphere, the center of the sphere and the radius, return a
2654 * point on the vector through the midpoint of the chord on the sphere.
2655 */
bisect_chord(vec3d * p0,vec3d * p1,vec3d * centerp,float radius)2656 void bisect_chord(vec3d *p0, vec3d *p1, vec3d *centerp, float radius)
2657 {
2658 vec3d tvec;
2659 vec3d new_pnt;
2660
2661 vm_vec_add(&tvec, p0, p1);
2662 vm_vec_sub2(&tvec, centerp);
2663 vm_vec_sub2(&tvec, centerp);
2664 if (vm_vec_mag_quick(&tvec) < 0.1f) {
2665 vm_vec_sub(&tvec, p0, p1);
2666 if (fl_abs(tvec.xyz.x) <= fl_abs(tvec.xyz.z)){
2667 tvec.xyz.x = -tvec.xyz.z;
2668 } else {
2669 tvec.xyz.y = -tvec.xyz.x;
2670 }
2671 }
2672
2673 vm_vec_normalize(&tvec);
2674 vm_vec_scale(&tvec, radius);
2675 vm_vec_add(&new_pnt, centerp, &tvec);
2676
2677 add_path_point(&new_pnt, -1, -1, -1);
2678 }
2679
2680 /**
2681 * Create a path from the current position to a goal position.
2682 *
2683 * The current position is in the current object and the goal position is in the goal object.
2684 * It is ok to intersect the current object, but not the goal object.
2685 * This function is useful for creating a path to an initial point near a large object.
2686 *
2687 * @param curpos Current position in vector space
2688 * @param goalpos Goal position in vector space
2689 * @param curobjp Current object pointer
2690 * @param goalobjp Goal object pointer
2691 * @param subsys_path Optional param (default 0), indicates this is a path to a subsystem
2692 */
create_path_to_point(vec3d * curpos,vec3d * goalpos,object * curobjp,object * goalobjp,int subsys_path)2693 void create_path_to_point(vec3d *curpos, vec3d *goalpos, object *curobjp, object *goalobjp, int subsys_path)
2694 {
2695 // If can't cast vector to goalpos, then create an intermediate point.
2696 if (pp_collide(curpos, goalpos, goalobjp, curobjp->radius)) {
2697 vec3d tan1;
2698 float radius;
2699
2700 // If this is a path to a subsystem, use SUBSYS_PATH_DIST as the radius for the object you are
2701 // trying to avoid. This is needed since subsystem paths extend out to SUBSYS_PATH_DIST, and we
2702 // want ships to reach their path destination without flying to points that sit on the radius of
2703 // a small ship
2704 radius = goalobjp->radius;
2705 if (subsys_path) {
2706 if ( SUBSYS_PATH_DIST > goalobjp->radius ) {
2707 radius = SUBSYS_PATH_DIST;
2708 }
2709 }
2710
2711 // The intermediate point is at the intersection of:
2712 // tangent to *goalobjp sphere at point *goalpos
2713 // tangent to *goalobjp sphere through point *curpos in plane defined by *curpos, *goalpos, goalobjp->pos
2714 // Note, there are two tangents through *curpos, unless *curpos is on the
2715 // sphere. The tangent that causes the nearer intersection (to *goalpos) is chosen.
2716 get_tangent_point(&tan1, curpos, &goalobjp->pos, goalpos, radius);
2717
2718 // If we can't reach tan1 from curpos, insert a new point.
2719 if (pp_collide(&tan1, curpos, goalobjp, curobjp->radius))
2720 bisect_chord(curpos, &tan1, &goalobjp->pos, radius);
2721
2722 add_path_point(&tan1, -1, -1, -1);
2723
2724 // If we can't reach goalpos from tan1, insert a new point.
2725 if (pp_collide(goalpos, &tan1, goalobjp, curobjp->radius))
2726 bisect_chord(goalpos, &tan1, &goalobjp->pos, radius);
2727 }
2728
2729 }
2730
2731 /**
2732 * Given an object and a model path, globalize the points on the model and copy into the global path list.
2733 * If pnp != NULL, then modify, in place, the path points. This is used to create new globalized points when the base object has moved.
2734 *
2735 * @param objp Object pointer
2736 * @param mp Model path
2737 * @param dir Directory type
2738 * @param count Count of items
2739 * @param path_num Path number
2740 * @param pnp Node on a path
2741 * @param randomize_pnt Optional parameter (default value -1), add random vector in sphere to this path point
2742 */
copy_xlate_model_path_points(object * objp,model_path * mp,int dir,int count,int path_num,pnode * pnp,int randomize_pnt)2743 void copy_xlate_model_path_points(object *objp, model_path *mp, int dir, int count, int path_num, pnode *pnp, int randomize_pnt)
2744 {
2745 int i;
2746 vec3d v1;
2747 int pp_index; // index in Path_points at which to store point, if this is a modify-in-place (pnp ! NULL)
2748 int start_index, finish_index;
2749 vec3d submodel_offset, local_vert;
2750 bool rotating_submodel;
2751
2752 // Initialize pp_index.
2753 // If pnp == NULL, that means we're creating new points. If not NULL, then modify in place.
2754 if (pnp == NULL)
2755 pp_index = -1; // This tells add_path_point to create a new point.
2756 else
2757 pp_index = 0; // pp_index will get assigned to index in Path_points to reuse.
2758
2759 if (dir == 1) {
2760 start_index = 0;
2761 finish_index = MIN(count, mp->nverts);
2762 } else {
2763 Assert(dir == -1); // direction must be up by 1 or down by 1 and it's neither!
2764 start_index = mp->nverts-1;
2765 finish_index = MAX(-1, mp->nverts-1-count);
2766 }
2767
2768 auto pmi = model_get_instance(Ships[objp->instance].model_instance_num);
2769 auto pm = model_get(pmi->model_num);
2770
2771 // Goober5000 - check for rotating submodels
2772 if ((mp->parent_submodel >= 0) && (pm->submodel[mp->parent_submodel].movement_type >= 0))
2773 {
2774 rotating_submodel = true;
2775
2776 model_find_submodel_offset(&submodel_offset, pm, mp->parent_submodel);
2777 }
2778 else
2779 {
2780 rotating_submodel = false;
2781 }
2782
2783 int offset = 0;
2784 for (i=start_index; i != finish_index; i += dir)
2785 {
2786 // Globalize the point.
2787 // Goober5000 - handle rotating submodels
2788 if (rotating_submodel)
2789 {
2790 // movement... find location of point like with docking code and spark generation
2791 vm_vec_sub(&local_vert, &mp->verts[i].pos, &submodel_offset);
2792 model_instance_find_world_point(&v1, &local_vert, pm, pmi, mp->parent_submodel, &objp->orient, &objp->pos);
2793 }
2794 else
2795 {
2796 // no movement... calculate as in original code
2797 vm_vec_unrotate(&v1, &mp->verts[i].pos, &objp->orient);
2798 vm_vec_add2(&v1, &objp->pos);
2799 }
2800
2801 if ( randomize_pnt == i ) {
2802 vec3d v_rand;
2803 static_randvec(OBJ_INDEX(objp), &v_rand);
2804 vm_vec_scale(&v_rand, 30.0f);
2805 vm_vec_add2(&v1, &v_rand);
2806 }
2807
2808 if (pp_index != -1)
2809 pp_index = (int)(pnp-Path_points) + offset;
2810
2811 add_path_point(&v1, path_num, i, pp_index);
2812 offset++;
2813 }
2814 }
2815
2816
2817 /**
2818 * For pl_objp, create a path along path path_num into mobjp.
2819 * The tricky part of this problem is creating the entry to the first point on the predefined path.
2820 * The points on this entry path are based on the location of Pl_objp relative to the start of the path.
2821 *
2822 * @param pl_objp Player object
2823 * @param mobjp Model object
2824 * @param path_num Number of path
2825 * @param subsys_path Optional param (default 0), indicating this is a path to a subsystem
2826 */
create_model_path(object * pl_objp,object * mobjp,int path_num,int subsys_path)2827 void create_model_path(object *pl_objp, object *mobjp, int path_num, int subsys_path)
2828 {
2829 ship *shipp = &Ships[pl_objp->instance];
2830 ai_info *aip = &Ai_info[shipp->ai_index];
2831
2832 ship_info *osip = &Ship_info[Ships[mobjp->instance].ship_info_index];
2833 polymodel *pm = model_get(Ship_info[Ships[mobjp->instance].ship_info_index].model_num);
2834 int num_points;
2835 model_path *mp;
2836 pnode *ppfp_start = Ppfp;
2837 vec3d gp0;
2838
2839 Assert(path_num >= 0);
2840
2841 // Do garbage collection if necessary.
2842 if (Ppfp-Path_points + 64 > MAX_PATH_POINTS) {
2843 garbage_collect_path_points();
2844 ppfp_start = Ppfp;
2845 }
2846
2847 aip->path_start = (int)(Ppfp - Path_points);
2848 Assert(path_num < pm->n_paths);
2849
2850 mp = &pm->paths[path_num];
2851 num_points = mp->nverts;
2852
2853 Assert(Ppfp-Path_points + num_points + 4 < MAX_PATH_POINTS);
2854
2855 vm_vec_unrotate(&gp0, &mp->verts[0].pos, &mobjp->orient);
2856 vm_vec_add2(&gp0, &mobjp->pos);
2857
2858 if (pp_collide(&pl_objp->pos, &gp0, mobjp, pl_objp->radius)) {
2859 vec3d perim_point1;
2860 vec3d perim_point2;
2861
2862 perim_point2 = pl_objp->pos;
2863
2864 // If object that wants to dock is inside bounding sphere of object it wants to dock with, make it fly out.
2865 // Assume it can fly "straight" out to the bounding sphere.
2866 if (vm_vec_dist_quick(&pl_objp->pos, &mobjp->pos) < mobjp->radius) {
2867 project_point_to_perimeter(&perim_point2, &mobjp->pos, mobjp->radius, &pl_objp->pos);
2868 add_path_point(&perim_point2, path_num, -1, -1);
2869 }
2870
2871 // If last point on pre-defined path is inside bounding sphere, create a new point on the surface of the sphere.
2872 if (vm_vec_dist_quick(&mobjp->pos, &gp0) < mobjp->radius) {
2873 project_point_to_perimeter(&perim_point1, &mobjp->pos, mobjp->radius, &gp0);
2874 create_path_to_point(&perim_point2, &perim_point1, pl_objp, mobjp, subsys_path);
2875 add_path_point(&perim_point1, path_num, -1, -1);
2876 } else { // The predefined path extends outside the sphere. Create path to that point.
2877 create_path_to_point(&perim_point2, &gp0, pl_objp, mobjp, subsys_path);
2878 }
2879 }
2880
2881 // AL 12-31-97: If following a subsystem path, add random vector to second last path point
2882 if ( subsys_path ) {
2883 copy_xlate_model_path_points(mobjp, mp, 1, mp->nverts, path_num, NULL, mp->nverts-2);
2884 } else {
2885 copy_xlate_model_path_points(mobjp, mp, 1, mp->nverts, path_num, NULL);
2886 }
2887
2888 aip->path_cur = aip->path_start;
2889 aip->path_dir = PD_FORWARD;
2890 aip->path_objnum = OBJ_INDEX(mobjp);
2891 aip->mp_index = path_num;
2892 aip->path_length = (int)(Ppfp - ppfp_start);
2893 aip->path_next_check_time = timestamp(1);
2894
2895 aip->path_goal_obj_hash = create_object_hash(&Objects[aip->path_objnum]);
2896
2897 aip->path_next_create_time = timestamp(1000); // OK to try to create one second later
2898 aip->path_create_pos = pl_objp->pos;
2899 aip->path_create_orient = pl_objp->orient;
2900
2901 //Get path departure orientation from ships.tbl if it exists, otherwise zero it
2902 SCP_string pathName(mp->name);
2903 if (osip->pathMetadata.find(pathName) != osip->pathMetadata.end() && !IS_VEC_NULL(&osip->pathMetadata[pathName].departure_rvec))
2904 {
2905 vm_vec_copy_normalize(&aip->path_depart_orient, &osip->pathMetadata[pathName].departure_rvec);
2906 }
2907 else
2908 {
2909 vm_vec_zero(&aip->path_depart_orient);
2910 }
2911
2912 aip->ai_flags.remove(AI::AI_Flags::Use_exit_path); // ensure this flag is cleared
2913 }
2914
2915 /**
2916 * For pl_objp, create a path along path path_num into mobjp.
2917 *
2918 * The tricky part of this problem is creating the entry to the first point on the predefined path.
2919 * The points on this entry path are based on the location of pl_objp relative to the start of the path.
2920 */
create_model_exit_path(object * pl_objp,object * mobjp,int path_num,int count)2921 void create_model_exit_path(object *pl_objp, object *mobjp, int path_num, int count)
2922 {
2923 ship *shipp = &Ships[pl_objp->instance];
2924 ai_info *aip = &Ai_info[shipp->ai_index];
2925
2926 polymodel *pm = model_get(Ship_info[Ships[mobjp->instance].ship_info_index].model_num);
2927 int num_points;
2928 model_path *mp;
2929 pnode *ppfp_start = Ppfp;
2930
2931 Assert(path_num >= 0);
2932
2933 // Do garbage collection if necessary.
2934 if (Ppfp-Path_points + 64 > MAX_PATH_POINTS) {
2935 garbage_collect_path_points();
2936 ppfp_start = Ppfp;
2937 }
2938
2939 aip->path_start = (int)(Ppfp - Path_points);
2940 Assert(path_num < pm->n_paths);
2941
2942 mp = &pm->paths[path_num];
2943 num_points = mp->nverts;
2944
2945 Assert(Ppfp-Path_points + num_points + 4 < MAX_PATH_POINTS);
2946
2947 copy_xlate_model_path_points(mobjp, mp, -1, count, path_num, NULL);
2948
2949 aip->path_cur = aip->path_start;
2950 aip->path_dir = PD_FORWARD;
2951 aip->path_objnum = OBJ_INDEX(mobjp);
2952 aip->mp_index = path_num;
2953 aip->path_length = (int)(Ppfp - ppfp_start);
2954 aip->path_next_check_time = timestamp(1);
2955
2956 aip->ai_flags.set(AI::AI_Flags::Use_exit_path); // mark as exit path, referenced in maybe
2957 }
2958
2959 /**
2960 * Return true if the vector from curpos to goalpos intersects with any ship other than the ignore objects.
2961 * Calls pp_collide()
2962 */
pp_collide_any(vec3d * curpos,vec3d * goalpos,float radius,object * ignore_objp1,object * ignore_objp2,int big_only_flag)2963 int pp_collide_any(vec3d *curpos, vec3d *goalpos, float radius, object *ignore_objp1, object *ignore_objp2, int big_only_flag)
2964 {
2965 ship_obj *so;
2966
2967 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
2968 object *objp = &Objects[so->objnum];
2969
2970 if (big_only_flag) {
2971 if (!Ship_info[Ships[objp->instance].ship_info_index].is_big_or_huge())
2972 continue;
2973 }
2974
2975 if ((objp != ignore_objp1) && (objp != ignore_objp2)) {
2976 if (pp_collide(curpos, goalpos, objp, radius))
2977 return OBJ_INDEX(objp);
2978 }
2979 }
2980
2981 return -1;
2982 }
2983
2984 // Used to create docking paths and other pre-defined paths through ships.
2985 // Creates a path in absolute space.
2986 // Create a path into the object objnum.
2987 //
2988 // input:
2989 // pl_objp: object that will use the path
2990 // objnum: Object to find path to.
2991 // path_num: model path index to use
2992 // exit_flag: true means this is an exit path in the model
2993 // subsys_path: optional param (default 0) that indicates this is a path to a subsystem
2994 // Exit:
2995 // ai_info struct in Pl_objp gets stuffed with information to enable Pl_objp to fly the path.
ai_find_path(object * pl_objp,int objnum,int path_num,int exit_flag,int subsys_path)2996 void ai_find_path(object *pl_objp, int objnum, int path_num, int exit_flag, int subsys_path)
2997 {
2998 ai_info *aip = &Ai_info[Ships[pl_objp->instance].ai_index];
2999
3000 Assert(path_num >= 0);
3001
3002 // This is test code, find an object with paths.
3003 if (objnum != -1) {
3004 object *objp = &Objects[objnum];
3005
3006 if (objp->type == OBJ_SHIP) {
3007 polymodel *pm;
3008
3009 ship *shipp = &Ships[objp->instance];
3010 pm = model_get(Ship_info[shipp->ship_info_index].model_num);
3011 if(path_num >= pm->n_paths)
3012 Error(LOCATION,"ai_find_path tring to find a path (%d) that doesn't exist, on ship %s", path_num, shipp->ship_name);
3013
3014 aip->goal_objnum = objnum;
3015 aip->goal_signature = objp->signature;
3016 if (exit_flag)
3017 create_model_exit_path(pl_objp, objp, path_num);
3018 else
3019 create_model_path(pl_objp, objp, path_num, subsys_path);
3020 return;
3021 }
3022
3023 }
3024 }
3025
3026 extern int vector_object_collision(vec3d *start_pos, vec3d *end_pos, object *objp, float radius_scale);
3027
3028 // Maybe make *objp avoid a player object.
3029 // For now, 4/6/98, only check Player_obj.
3030 // If player collision would occur, set AIF_AVOIDING_SMALL_SHIP bit in ai_flags.
3031 // Set aip->avoid_goal_point
maybe_avoid_player(object * objp,vec3d * goal_pos)3032 int maybe_avoid_player(object *objp, vec3d *goal_pos)
3033 {
3034 ai_info *aip;
3035 vec3d cur_pos, new_goal_pos;
3036 object *player_objp;
3037 vec3d n_vec_to_goal, n_vec_to_player;
3038
3039 aip = &Ai_info[Ships[objp->instance].ai_index];
3040
3041 if (!timestamp_elapsed(aip->avoid_check_timestamp))
3042 return 0;
3043
3044 player_objp = Player_obj;
3045
3046 float speed_time;
3047
3048 // How far two ships could be apart and still collide within one second.
3049 speed_time = player_objp->phys_info.speed + objp->phys_info.speed;
3050
3051 float obj_obj_dist;
3052
3053 obj_obj_dist = vm_vec_dist_quick(&player_objp->pos, &objp->pos);
3054
3055 if (obj_obj_dist > speed_time*2.0f)
3056 return 0;
3057
3058 cur_pos = objp->pos;
3059
3060 new_goal_pos = *goal_pos;
3061
3062 float dist = vm_vec_normalized_dir(&n_vec_to_goal, goal_pos, &objp->pos);
3063 vm_vec_normalized_dir(&n_vec_to_player, &player_objp->pos, &objp->pos);
3064
3065 if (dist > speed_time*2.0f) {
3066 vm_vec_scale_add(&new_goal_pos, &objp->pos, &n_vec_to_goal, 200.0f);
3067 }
3068
3069 if (vector_object_collision(&objp->pos, &new_goal_pos, player_objp, 1.5f)) {
3070 aip->ai_flags.set(AI::AI_Flags::Avoiding_small_ship);
3071
3072 vec3d avoid_vec;
3073
3074 vm_vec_sub(&avoid_vec, &n_vec_to_goal, &n_vec_to_player);
3075 if (vm_vec_mag_quick(&avoid_vec) < 0.01f) {
3076 vm_vec_copy_scale(&avoid_vec, &objp->orient.vec.rvec, frand()-0.5f);
3077 vm_vec_scale_add2(&avoid_vec, &objp->orient.vec.uvec, frand()-0.5f);
3078 vm_vec_normalize(&avoid_vec);
3079 } else {
3080 vec3d tvec1;
3081 vm_vec_normalize(&avoid_vec);
3082 vm_vec_cross(&tvec1, &n_vec_to_goal, &avoid_vec);
3083 vm_vec_cross(&avoid_vec, &tvec1, &n_vec_to_player);
3084 }
3085
3086 // Now, avoid_vec is a vector perpendicular to the vector to the player and the direction *objp
3087 // should fly in to avoid the player while still approaching its goal.
3088 vm_vec_scale_add(&aip->avoid_goal_point, &player_objp->pos, &avoid_vec, 400.0f);
3089
3090 aip->avoid_check_timestamp = timestamp(1000);
3091
3092 return 1;
3093 } else {
3094 aip->ai_flags.remove(AI::AI_Flags::Avoiding_small_ship);
3095 aip->avoid_check_timestamp = timestamp((int) (obj_obj_dist/200.0f) + 500);
3096
3097 return 0;
3098 }
3099 }
3100
3101 // Make object *still_objp enter AIM_STILL mode.
3102 // Make it point at view_pos.
ai_stay_still(object * still_objp,vec3d * view_pos)3103 void ai_stay_still(object *still_objp, vec3d *view_pos)
3104 {
3105 ship *shipp;
3106 ai_info *aip;
3107
3108 Assert(still_objp->type == OBJ_SHIP);
3109 Assert((still_objp->instance >= 0) && (still_objp->instance < MAX_OBJECTS));
3110
3111 shipp = &Ships[still_objp->instance];
3112 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
3113
3114 aip = &Ai_info[shipp->ai_index];
3115
3116 aip->mode = AIM_STILL;
3117
3118 // If view_pos not NULL, point at that point. Else, point at a point directly in front of ship. Ie, don't turn.
3119 if (view_pos != NULL)
3120 aip->goal_point = *view_pos;
3121 else
3122 vm_vec_scale_add(&aip->goal_point, &still_objp->pos, &still_objp->orient.vec.fvec, 100.0f);
3123 }
3124
3125 // code which is called from ai_dock_with_object and ai_dock to set flags and apprioriate variable
3126 // when two objects have completed docking. used because we can dock object initially at misison load
3127 // time (meaning that ai_dock() might never get called). docker has docked with dockee (i.e. docker
3128 // would be a freighter and dockee would be a cargo).
ai_do_objects_docked_stuff(object * docker,int docker_point,object * dockee,int dockee_point,bool update_clients)3129 void ai_do_objects_docked_stuff(object *docker, int docker_point, object *dockee, int dockee_point, bool update_clients)
3130 {
3131 Assert((docker != NULL) && (dockee != NULL));
3132
3133 // make sure they're not already docked!
3134 if (dock_check_find_direct_docked_object(docker, dockee))
3135 {
3136 Warning(LOCATION, "Call to ai_do_objects_docked_stuff when objects are already docked! Trace out and fix!\n");
3137 return;
3138 }
3139
3140 // link the two objects
3141 dock_dock_objects(docker, docker_point, dockee, dockee_point);
3142
3143 if (docker->type == OBJ_SHIP && dockee->type == OBJ_SHIP)
3144 {
3145 // maybe set support ship info
3146 if ((Ship_info[Ships[docker->instance].ship_info_index].flags[Ship::Info_Flags::Support])
3147 || (Ship_info[Ships[dockee->instance].ship_info_index].flags[Ship::Info_Flags::Support]))
3148 {
3149 ai_info *docker_aip = &Ai_info[Ships[docker->instance].ai_index];
3150 ai_info *dockee_aip = &Ai_info[Ships[dockee->instance].ai_index];
3151
3152 #ifndef NDEBUG
3153 // support ship can only dock with one thing at a time
3154 if (Ship_info[Ships[docker->instance].ship_info_index].flags[Ship::Info_Flags::Support])
3155 Assert(docker->dock_list->next == NULL);
3156 else
3157 Assert(dockee->dock_list->next == NULL);
3158 #endif
3159
3160 // set stuff for both objects
3161 docker_aip->support_ship_objnum = OBJ_INDEX(dockee);
3162 dockee_aip->support_ship_objnum = OBJ_INDEX(docker);
3163 docker_aip->support_ship_signature = dockee->signature;
3164 dockee_aip->support_ship_signature = docker->signature;
3165 }
3166 }
3167
3168 // add multiplayer hook here to deal with docked objects.
3169 if ( MULTIPLAYER_MASTER && update_clients)
3170 send_ai_info_update_packet( docker, AI_UPDATE_DOCK, dockee );
3171 }
3172
3173 // code which is called when objects become undocked. Equivalent of above function.
3174 // Goober5000 - dockee must always be non-NULL
ai_do_objects_undocked_stuff(object * docker,object * dockee)3175 void ai_do_objects_undocked_stuff( object *docker, object *dockee )
3176 {
3177 Assert((docker != NULL) && (dockee != NULL));
3178
3179 // make sure they're not already undocked!
3180 if (!dock_check_find_direct_docked_object(docker, dockee))
3181 {
3182 Warning(LOCATION, "Call to ai_do_objects_undocked_stuff when objects are already undocked! Trace out and fix!\n");
3183 return;
3184 }
3185
3186 // add multiplayer hook here to deal with undocked objects. Do it before we
3187 // do anything else. We don't need to send info for both objects, since multi
3188 // only supports one docked object
3189 if ( MULTIPLAYER_MASTER )
3190 send_ai_info_update_packet( docker, AI_UPDATE_UNDOCK, dockee );
3191
3192 if (docker->type == OBJ_SHIP && dockee->type == OBJ_SHIP)
3193 {
3194 ai_info *docker_aip = &Ai_info[Ships[docker->instance].ai_index];
3195 ai_info *dockee_aip = &Ai_info[Ships[dockee->instance].ai_index];
3196
3197 // clear stuff for both objects
3198 docker_aip->ai_flags.remove(AI::AI_Flags::Being_repaired);
3199 dockee_aip->ai_flags.remove(AI::AI_Flags::Being_repaired);
3200 docker_aip->support_ship_objnum = -1;
3201 dockee_aip->support_ship_objnum = -1;
3202 docker_aip->support_ship_signature = -1;
3203 dockee_aip->support_ship_signature = -1;
3204 }
3205
3206 // unlink the two objects
3207 dock_undock_objects(docker, dockee);
3208 }
3209
3210
3211 // --------------------------------------------------------------------------
3212 // Interface from goals code to AI.
3213 // Cause *docker to dock with *dockee.
3214 // priority is priority of goal from goals code.
3215 // dock_type is:
3216 // AIDO_DOCK set goal of docking
3217 // AIDO_DOCK_NOW immediately dock, used for ships that need to be docked at mission start
3218 // AIDO_UNDOCK set goal of undocking
ai_dock_with_object(object * docker,int docker_index,object * dockee,int dockee_index,int dock_type)3219 void ai_dock_with_object(object *docker, int docker_index, object *dockee, int dockee_index, int dock_type)
3220 {
3221 Assert(docker != NULL);
3222 Assert(dockee != NULL);
3223 Assert(docker->type == OBJ_SHIP);
3224 Assert(dockee->type == OBJ_SHIP);
3225 Assert(docker->instance >= 0);
3226 Assert(dockee->instance >= 0);
3227 Assert(Ships[docker->instance].ai_index >= 0);
3228 Assert(Ships[dockee->instance].ai_index >= 0);
3229 Assert(docker_index >= 0);
3230 Assert(dockee_index >= 0);
3231
3232 ai_info *aip = &Ai_info[Ships[docker->instance].ai_index];
3233
3234 aip->goal_objnum = OBJ_INDEX(dockee);
3235 aip->goal_signature = dockee->signature;
3236
3237 aip->mode = AIM_DOCK;
3238
3239 switch (dock_type) {
3240 case AIDO_DOCK:
3241 aip->submode = AIS_DOCK_0;
3242 aip->submode_start_time = Missiontime;
3243 break;
3244 case AIDO_DOCK_NOW:
3245 aip->submode = AIS_DOCK_4A;
3246 aip->submode_start_time = Missiontime;
3247 break;
3248 case AIDO_UNDOCK:
3249 aip->submode = AIS_UNDOCK_0;
3250 aip->submode_start_time = Missiontime;
3251 break;
3252 default:
3253 Int3(); // Bogus dock_type.
3254 }
3255
3256 // Goober5000 - we no longer need to set dock_path_index because it's easier to grab the path from the dockpoint
3257 // a debug check would be a good thing here, though
3258 #ifndef NDEBUG
3259 if (dock_type == AIDO_UNDOCK)
3260 {
3261 polymodel *pm = model_get(Ship_info[Ships[dockee->instance].ship_info_index].model_num);
3262 Assert( pm->docking_bays[dockee_index].num_spline_paths > 0 );
3263 }
3264 #endif
3265
3266 // dock instantly
3267 if (dock_type == AIDO_DOCK_NOW)
3268 {
3269 // set model animations correctly
3270 // (fortunately, this function is called AFTER model_anim_set_initial_states in the sea of ship creation
3271 // functions, which is necessary for model animations to start from t=0 at the correct positions)
3272 ship *shipp = &Ships[docker->instance];
3273 ship *goal_shipp = &Ships[dockee->instance];
3274 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage1, docker_index, 1, true);
3275 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, 1, true);
3276 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, 1, true);
3277 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, 1, true);
3278 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, 1, true);
3279 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, 1, true);
3280 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, 1, true);
3281 model_anim_start_type(goal_shipp, AnimationTriggerType::Docked, dockee_index, 1, true);
3282
3283 dock_orient_and_approach(docker, docker_index, dockee, dockee_index, DOA_DOCK_STAY);
3284 ai_do_objects_docked_stuff( docker, docker_index, dockee, dockee_index, false );
3285 }
3286 // pick a path to use to start docking
3287 else
3288 {
3289 int path_num = ai_return_path_num_from_dockbay(dockee, dockee_index);
3290
3291 // make sure we have a path
3292 if (path_num < 0)
3293 {
3294 Error(LOCATION, "Cannot find a dock path for ship %s, dock index %d. Aborting dock.\n", Ships[dockee->instance].ship_name, dockee_index);
3295 ai_mission_goal_complete(aip);
3296 return;
3297 }
3298
3299 ai_find_path(docker, OBJ_INDEX(dockee), path_num, 0);
3300 }
3301 }
3302
3303
3304 /*
3305 * Cause a ship to fly toward a ship.
3306 */
ai_start_fly_to_ship(object * objp,int shipnum)3307 void ai_start_fly_to_ship(object *objp, int shipnum)
3308 {
3309 ai_info *aip;
3310
3311 aip = &Ai_info[Ships[objp->instance].ai_index];
3312
3313 if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics] && AutoPilotEngaged)
3314 {
3315 aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
3316 }
3317 else
3318 {
3319 aip->ai_flags.set(AI::AI_Flags::Formation_wing);
3320 }
3321 aip->ai_flags.remove(AI::AI_Flags::Formation_object);
3322
3323 aip->mode = AIM_FLY_TO_SHIP;
3324 aip->submode_start_time = Missiontime;
3325
3326 aip->target_objnum = Ships[shipnum].objnum;
3327 // clear targeted subsystem
3328 set_targeted_subsys(aip, nullptr, -1);
3329
3330 Assert(aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC);
3331 }
3332
3333
3334 // Cause a ship to fly its waypoints.
3335 // flags tells:
3336 // WPF_REPEAT Set -> repeat waypoints.
ai_start_waypoints(object * objp,waypoint_list * wp_list,int wp_flags)3337 void ai_start_waypoints(object *objp, waypoint_list *wp_list, int wp_flags)
3338 {
3339 ai_info *aip;
3340 Assert(wp_list != NULL);
3341
3342 aip = &Ai_info[Ships[objp->instance].ai_index];
3343
3344 if ( (aip->mode == AIM_WAYPOINTS) && (aip->wp_list == wp_list) )
3345 {
3346 if (aip->wp_index == INVALID_WAYPOINT_POSITION)
3347 {
3348 Warning(LOCATION, "aip->wp_index should have been assigned already!");
3349 aip->wp_index = 0;
3350 }
3351 return;
3352 }
3353
3354 if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics] && AutoPilotEngaged)
3355 {
3356 aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
3357 }
3358 else
3359 {
3360 aip->ai_flags.set(AI::AI_Flags::Formation_wing);
3361 }
3362 aip->ai_flags.remove(AI::AI_Flags::Formation_object);
3363
3364 aip->wp_list = wp_list;
3365 aip->wp_index = 0;
3366 aip->wp_flags = wp_flags;
3367 aip->mode = AIM_WAYPOINTS;
3368
3369 Assert(aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC);
3370 }
3371
3372 /**
3373 * Make *objp stay within dist units of *other_objp
3374 */
ai_do_stay_near(object * objp,object * other_objp,float dist)3375 void ai_do_stay_near(object *objp, object *other_objp, float dist)
3376 {
3377 ai_info *aip;
3378
3379 Assert(objp != other_objp); // Bogus! Told to stay near self.
3380 Assert(objp->type == OBJ_SHIP);
3381 Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
3382
3383 aip = &Ai_info[Ships[objp->instance].ai_index];
3384
3385 aip->mode = AIM_STAY_NEAR;
3386 aip->submode = -1;
3387 aip->submode_start_time = Missiontime;
3388 aip->stay_near_distance = dist;
3389 aip->goal_objnum = OBJ_INDEX(other_objp);
3390 aip->goal_signature = other_objp->signature;
3391 }
3392
3393 // Goober5000 - enter safety mode (used by support ships, now possibly by fighters too)
ai_do_safety(object * objp)3394 void ai_do_safety(object *objp)
3395 {
3396 ai_info *aip;
3397
3398 Assert(objp->type == OBJ_SHIP);
3399 Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
3400
3401 aip = &Ai_info[Ships[objp->instance].ai_index];
3402
3403 aip->mode = AIM_SAFETY;
3404 aip->submode = AISS_1;
3405 aip->submode_start_time = Missiontime;
3406 }
3407
3408 /**
3409 * Make object *objp form on wing of object *goal_objp
3410 */
ai_form_on_wing(object * objp,object * goal_objp)3411 void ai_form_on_wing(object *objp, object *goal_objp)
3412 {
3413 ai_info *aip;
3414 ship *shipp;
3415 ship_info *sip;
3416
3417 // objp == goal_objp sometimes in multiplayer when someone leaves a game -- make a simple
3418 // out for this case.
3419 if ( Game_mode & GM_MULTIPLAYER ) {
3420 if ( objp == goal_objp ) {
3421 return;
3422 }
3423 }
3424
3425 Assert(objp != goal_objp); // Bogus! Told to form on own's wing!
3426
3427 shipp = &Ships[objp->instance];
3428 sip = &Ship_info[shipp->ship_info_index];
3429
3430 // Only fighters or bombers allowed to form on wing.
3431 if (!(sip->is_fighter_bomber())) {
3432 nprintf(("AI", "Warning: Ship %s tried to form on player's wing, but not fighter or bomber.\n", shipp->ship_name));
3433 return;
3434 }
3435
3436
3437 aip = &Ai_info[Ships[objp->instance].ai_index];
3438
3439 aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
3440 aip->ai_flags.set(AI::AI_Flags::Formation_object);
3441
3442 aip->goal_objnum = OBJ_INDEX(goal_objp);
3443
3444 ai_formation_object_recalculate_slotnums(aip->goal_objnum);
3445
3446 ai_set_goal_abort_support_call(objp, aip);
3447 aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME*4); // Super extra long time until can target another ship.
3448
3449 }
3450
3451 /**
3452 * Redistributes slotnums to all ships flying off a given formation object
3453 * In the case this is needed because it has left the field or died, its objnum is needed to explicitly exclude it
3454 */
ai_formation_object_recalculate_slotnums(int form_objnum,int exiting_objnum)3455 void ai_formation_object_recalculate_slotnums(int form_objnum, int exiting_objnum)
3456 {
3457 Assertion(form_objnum >= 0, "Invalid object number in ai_formation_object_recalculate_slotnums!");
3458 int slotnum = 1; // Note: Slot #0 means leader, which isn't someone who was told to form-on-wing.
3459
3460 for (ship_obj* ship = GET_FIRST(&Ship_obj_list); ship != END_OF_LIST(&Ship_obj_list); ship = GET_NEXT(ship) ) {
3461 ai_info* aip = &Ai_info[Ships[Objects[ship->objnum].instance].ai_index];
3462
3463 if (ship->objnum == exiting_objnum)
3464 continue;
3465
3466 if (aip->ai_flags[AI::AI_Flags::Formation_object]) {
3467 if (aip->goal_objnum == form_objnum) {
3468 aip->form_obj_slotnum = slotnum;
3469 slotnum++;
3470 }
3471 }
3472 }
3473 }
3474
3475 #define BIGNUM 100000.0f
3476
3477 int Debug_k = 0;
3478
3479 /**
3480 * Given an attacker's position and a target's position and velocity, compute the time of
3481 * intersection of a weapon fired by the attacker with speed weapon_speed.
3482 *
3483 * Return this value. Return value of 0.0f means no collision is possible.
3484 */
compute_collision_time(vec3d * targpos,vec3d * targvel,vec3d * attackpos,float weapon_speed)3485 float compute_collision_time(vec3d *targpos, vec3d *targvel, vec3d *attackpos, float weapon_speed)
3486 {
3487 vec3d vec_to_target;
3488 float pos_dot_vel;
3489 float vel_sqr;
3490 float discrim;
3491
3492 vm_vec_sub(&vec_to_target, targpos, attackpos);
3493 pos_dot_vel = vm_vec_dot(&vec_to_target, targvel);
3494 vel_sqr = vm_vec_dot(targvel, targvel) - weapon_speed*weapon_speed;
3495 discrim = pos_dot_vel*pos_dot_vel - vel_sqr*vm_vec_dot(&vec_to_target, &vec_to_target);
3496
3497 if (discrim > 0.0f) {
3498 float t1, t2, t_solve;
3499
3500 t1 = (-pos_dot_vel + fl_sqrt(discrim)) / vel_sqr;
3501 t2 = (-pos_dot_vel - fl_sqrt(discrim)) / vel_sqr;
3502
3503 t_solve = BIGNUM;
3504
3505 if (t1 > 0.0f)
3506 t_solve = t1;
3507 if ((t2 > 0.0f) && (t2 < t_solve))
3508 t_solve = t2;
3509
3510 if (t_solve < BIGNUM-1.0f) {
3511 return t_solve + Debug_k * flFrametime;
3512 }
3513 }
3514
3515 return 0.0f;
3516 }
3517
3518
3519 // --------------------------------------------------------------------------
3520 // If far away, use player's speed.
3521 // If in between, lerp between player and laser speed
3522 // If close, use laser speed.
3523 // Want to know how much time it will take to get to the enemy.
3524 // This function doesn't account for the fact that by the time the player
3525 // (or his laser) gets to the current enemy position, the enemy will have moved.
3526 // This is dealt with in polish_predicted_enemy_pos.
compute_time_to_enemy(float dist_to_enemy,object * pobjp)3527 float compute_time_to_enemy(float dist_to_enemy, object *pobjp)
3528 {
3529 float time_to_enemy;
3530 float pl_speed = pobjp->phys_info.speed;
3531 float max_laser_distance, max_laser_speed;
3532 int bank_num, weapon_num;
3533 ship *shipp = &Ships[pobjp->instance];
3534
3535 bank_num = shipp->weapons.current_primary_bank;
3536 weapon_num = shipp->weapons.primary_bank_weapons[bank_num];
3537 max_laser_speed = Weapon_info[weapon_num].max_speed;
3538 max_laser_distance = MIN((max_laser_speed * Weapon_info[weapon_num].lifetime), Weapon_info[weapon_num].weapon_range);
3539
3540 // If pretty far away, use player's speed to predict position, else
3541 // use laser's speed because when close, we care more about hitting
3542 // with a laser than about causing ship:ship rendezvous.
3543 if (dist_to_enemy > 1.5 * max_laser_distance) {
3544 if (pl_speed > 0.0f)
3545 time_to_enemy = dist_to_enemy/pl_speed;
3546 else
3547 time_to_enemy = 1.0f;
3548 } else if (dist_to_enemy > 1.1*max_laser_distance) {
3549 if (pl_speed > 0.1f) {
3550 float scale;
3551
3552 scale = (float) ((dist_to_enemy - max_laser_distance) / max_laser_distance);
3553
3554 time_to_enemy = (float) (dist_to_enemy/(pl_speed * scale + max_laser_speed * (1.0f - scale)));
3555 } else
3556 time_to_enemy = 2.0f;
3557 } else
3558 time_to_enemy = (float) (dist_to_enemy/max_laser_speed);
3559
3560 return time_to_enemy + flFrametime;
3561 }
3562
3563 // --------------------------------------------------------------------------
ai_set_positions(object * pl_objp,object * en_objp,ai_info * aip,vec3d * player_pos,vec3d * enemy_pos)3564 void ai_set_positions(object *pl_objp, object *en_objp, ai_info *aip, vec3d *player_pos, vec3d *enemy_pos)
3565 {
3566 *player_pos = pl_objp->pos;
3567
3568 if (aip->next_predict_pos_time > Missiontime) {
3569 *enemy_pos = aip->last_predicted_enemy_pos;
3570 } else {
3571 *enemy_pos = en_objp->pos;
3572
3573 aip->next_predict_pos_time = Missiontime + aip->ai_predict_position_delay;
3574 aip->last_predicted_enemy_pos = *enemy_pos;
3575 }
3576
3577
3578 }
3579
3580 /**
3581 * Updates AI aim automatically based on targetted enemy.
3582 *
3583 * @param aip Pointer to AI info
3584 */
ai_update_aim(ai_info * aip)3585 void ai_update_aim(ai_info *aip)
3586 {
3587 if (Missiontime >= aip->next_aim_pos_time)
3588 {
3589 aip->last_aim_enemy_pos = En_objp->pos;
3590 aip->last_aim_enemy_vel = En_objp->phys_info.vel;
3591 aip->next_aim_pos_time = Missiontime + fl2f(frand_range(0.0f, aip->ai_max_aim_update_delay));
3592 }
3593 else
3594 {
3595 //Update the position based on the velocity (assume no velocity vector change)
3596 vm_vec_scale_add2(&aip->last_aim_enemy_pos, &aip->last_aim_enemy_vel, flFrametime);
3597 }
3598 }
3599
3600 // Given an ai_info struct, by reading current goal and path information,
3601 // extract base path information and return in pmp and pmpv.
3602 // Return true if found, else return false.
3603 // false means the current point is not on the original path.
get_base_path_info(int path_cur,int goal_objnum,model_path ** pmp,mp_vert ** pmpv)3604 int get_base_path_info(int path_cur, int goal_objnum, model_path **pmp, mp_vert **pmpv)
3605 {
3606 pnode *pn = &Path_points[path_cur];
3607 ship *shipp = &Ships[Objects[goal_objnum].instance];
3608 polymodel *pm = model_get(Ship_info[shipp->ship_info_index].model_num);
3609
3610 *pmpv = NULL;
3611 *pmp = NULL;
3612
3613 if (pn->path_num != -1) {
3614 *pmp = &pm->paths[pn->path_num];
3615 if (pn->path_index != -1)
3616 *pmpv = &(*pmp)->verts[pn->path_index];
3617 else
3618 return 0;
3619 } else
3620 return 0;
3621
3622 return 1;
3623 }
3624
3625 // Modify, in place, the points in a global model path.
3626 // Only modify those points that are defined in the model path. Don't modify the
3627 // leadin points, such as those that are necessary to get the model on the path.
modify_model_path_points(object * objp)3628 void modify_model_path_points(object *objp)
3629 {
3630 ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
3631 object *mobjp = &Objects[aip->path_objnum];
3632 polymodel *pm = model_get(Ship_info[Ships[mobjp->instance].ship_info_index].model_num);
3633 pnode *pnp;
3634 int path_num, dir;
3635
3636 Assert((aip->path_start >= 0) && (aip->path_start < MAX_PATH_POINTS));
3637
3638 pnp = &Path_points[aip->path_start];
3639 while ((pnp->path_index == -1) && (pnp-Path_points - aip->path_start < aip->path_length))
3640 pnp++;
3641
3642 path_num = pnp->path_num;
3643 Assert((path_num >= 0) && (path_num < pm->n_paths));
3644
3645 Assert(pnp->path_index != -1); // If this is -1, that means we never found the model path points
3646
3647 dir = 1;
3648 if ( aip->ai_flags[AI::AI_Flags::Use_exit_path] ) {
3649 dir = -1;
3650 }
3651
3652 copy_xlate_model_path_points(mobjp, &pm->paths[path_num], dir, pm->paths[path_num].nverts, path_num, pnp);
3653 }
3654
3655 // Return an indication of the distance between two matrices.
3656 // This is the sum of the distances of their dot products from 1.0f.
ai_matrix_dist(matrix * mat1,matrix * mat2)3657 float ai_matrix_dist(matrix *mat1, matrix *mat2)
3658 {
3659 float t;
3660
3661 t = 1.0f - vm_vec_dot(&mat1->vec.fvec, &mat2->vec.fvec);
3662 t += 1.0f - vm_vec_dot(&mat1->vec.uvec, &mat2->vec.uvec);
3663 t += 1.0f - vm_vec_dot(&mat1->vec.rvec, &mat2->vec.rvec);
3664
3665 return t;
3666 }
3667
3668
3669 // Paths are created in absolute space, so a moving object needs to have model paths within it recreated.
3670 // This uses the hash functions which means the slightest movement will cause a recreate, though the timestamp
3671 // prevents this from happening too often.
3672 // force_recreate_flag TRUE means to recreate regardless of timestamp.
3673 // Returns TRUE if path recreated.
maybe_recreate_path(object * objp,ai_info * aip,int force_recreate_flag,int override_hash=0)3674 float maybe_recreate_path(object *objp, ai_info *aip, int force_recreate_flag, int override_hash = 0)
3675 {
3676 int hashval;
3677
3678 Assert(&Ai_info[Ships[objp->instance].ai_index] == aip);
3679
3680 if ((aip->mode == AIM_BAY_EMERGE) || (aip->mode == AIM_BAY_DEPART))
3681 if ((OBJ_INDEX(objp) % 4) == (Framecount % 4))
3682 force_recreate_flag = 1;
3683
3684 // If no path, that means we don't need one.
3685 if (aip->path_start == -1)
3686 return 0.0f;
3687
3688 // AL 11-12-97: If AIF_USE_STATIC_PATH is set, don't try to recreate. This is needed when ships
3689 // emerge from fighter bays. We don't need to recreate the path.. and in case the
3690 // parent ship dies, we still want to be able to continue on the path
3691 if ( aip->ai_flags[AI::AI_Flags::Use_static_path] )
3692 return 0.0f;
3693
3694 if (force_recreate_flag || timestamp_elapsed(aip->path_next_create_time)) {
3695 object *path_objp;
3696
3697 path_objp = &Objects[aip->path_objnum];
3698 hashval = create_object_hash(path_objp);
3699
3700 if (override_hash || (hashval != aip->path_goal_obj_hash)) {
3701 float dist;
3702
3703 dist = vm_vec_dist_quick(&path_objp->pos, &aip->path_create_pos);
3704 dist += ai_matrix_dist(&path_objp->orient, &aip->path_create_orient) * 25.0f;
3705
3706 if (force_recreate_flag || (dist > 2.0f)) {
3707 aip->path_next_create_time = timestamp(1000); // Update again in as little as 1000 milliseconds, ie 1 second.
3708 aip->path_goal_obj_hash = hashval;
3709 modify_model_path_points(objp);
3710
3711 aip->path_create_pos = path_objp->pos;
3712 aip->path_create_orient = path_objp->orient;
3713
3714 //Get path departure orientation from ships.tbl if it exists, otherwise zero it
3715 ship_info *osip = &Ship_info[Ships[path_objp->instance].ship_info_index];
3716 model_path *mp = &model_get(osip->model_num)->paths[aip->mp_index];
3717 SCP_string pathName(mp->name);
3718 if (osip->pathMetadata.find(pathName) != osip->pathMetadata.end() && !IS_VEC_NULL(&osip->pathMetadata[pathName].departure_rvec))
3719 {
3720 vm_vec_copy_normalize(&aip->path_depart_orient, &osip->pathMetadata[pathName].departure_rvec);
3721 }
3722 else
3723 {
3724 vm_vec_zero(&aip->path_depart_orient);
3725 }
3726
3727 return dist;
3728 }
3729 }
3730 }
3731
3732 return 0.0f;
3733 }
3734
3735 /**
3736 * Set acceleration for ai_dock().
3737 */
set_accel_for_docking(object * objp,ai_info * aip,float dot,float dot_to_next,float dist_to_next,float dist_to_goal,ship_info * sip,float max_allowed_speed,object * gobjp)3738 void set_accel_for_docking(object *objp, ai_info *aip, float dot, float dot_to_next, float dist_to_next, float dist_to_goal, ship_info *sip, float max_allowed_speed, object *gobjp)
3739 {
3740 float prev_dot_to_goal = aip->prev_dot_to_goal;
3741
3742 aip->prev_dot_to_goal = dot;
3743
3744 if (objp->phys_info.speed < 0.0f) {
3745 accelerate_ship(aip, 1.0f/32.0f);
3746 } else if ((prev_dot_to_goal-dot) > 0.01) {
3747 if (prev_dot_to_goal > dot + 0.05f) {
3748 accelerate_ship(aip, 0.0f);
3749 } else {
3750 change_acceleration(aip, -1.0f); // -1.0f means subtract off flFrametime from acceleration value in 0.0..1.0
3751 }
3752 } else {
3753 float max_bay_speed = sip->max_speed;
3754
3755 // Maybe gradually ramp up/down the speed of a ship flying a fighterbay path
3756 if (aip->mode == AIM_BAY_EMERGE || (aip->mode == AIM_BAY_DEPART && aip->path_cur != aip->path_start)) {
3757 ship_info *gsip = &Ship_info[Ships[gobjp->instance].ship_info_index];
3758 polymodel *pm = model_get(gsip->model_num);
3759 SCP_string pathName(pm->paths[Path_points[aip->path_start].path_num].name);
3760 float speed_mult = FLT_MIN;
3761
3762 if (aip->mode == AIM_BAY_EMERGE) { // Arriving
3763 if (gsip->pathMetadata.find(pathName) != gsip->pathMetadata.end()) {
3764 speed_mult = gsip->pathMetadata[pathName].arrive_speed_mult;
3765 }
3766
3767 if (speed_mult == FLT_MIN) {
3768 speed_mult = The_mission.ai_profile->bay_arrive_speed_mult;
3769 }
3770 } else { // Departing
3771 if (gsip->pathMetadata.find(pathName) != gsip->pathMetadata.end()) {
3772 speed_mult = gsip->pathMetadata[pathName].depart_speed_mult;
3773 }
3774
3775 if (speed_mult == FLT_MIN) {
3776 speed_mult = The_mission.ai_profile->bay_depart_speed_mult;
3777 }
3778 }
3779
3780 if (speed_mult != FLT_MIN && speed_mult != 1.0f) {
3781 // We use the distance between the first and last point on the path here; it's not accurate
3782 // if the path is not straight, but should be good enough usually; can be changed if necessary.
3783 float total_path_length = vm_vec_dist_quick(&Path_points[aip->path_start].pos, &Path_points[aip->path_start + aip->path_length - 1].pos);
3784 float dist_to_end;
3785
3786 if (aip->mode == AIM_BAY_EMERGE) { // Arriving
3787 dist_to_end = vm_vec_dist_quick(&Pl_objp->pos, &Path_points[aip->path_start].pos);
3788 } else { // Departing
3789 dist_to_end = vm_vec_dist_quick(&Pl_objp->pos, &Path_points[aip->path_start + aip->path_length - 1].pos);
3790 }
3791
3792 // Calculate max speed, but respect the waypoint speed cap if it's lower
3793 max_bay_speed = sip->max_speed * (speed_mult + (1.0f - speed_mult) * (dist_to_end / total_path_length));
3794 }
3795 }
3796
3797 // Cap speed to max_allowed_speed, if it's set
3798 if (max_allowed_speed <= 0)
3799 max_allowed_speed = max_bay_speed;
3800 else
3801 max_allowed_speed = MIN(max_allowed_speed, max_bay_speed);
3802
3803 if ((aip->mode == AIM_DOCK) && (dist_to_next < 150.0f) && (aip->path_start + aip->path_length - 2 == aip->path_cur)) {
3804 set_accel_for_target_speed(objp, max_allowed_speed * MAX(dist_to_next/500.0f, 1.0f));
3805 } else if ((dot_to_next >= dot * .9) || (dist_to_next > 100.0f)) {
3806 if (dist_to_goal > 200.0f) {
3807 set_accel_for_target_speed(objp, max_allowed_speed * (dot + 1.0f) / 2.0f);
3808 }
3809 else {
3810 float xdot;
3811
3812 xdot = (dot_to_next + dot)/2.0f;
3813 if (xdot < 0.0f)
3814 xdot = 0.0f;
3815
3816 // AL: if following a path not in dock mode, move full speed
3817 if (( aip->mode != AIM_DOCK ) && (dot > 0.9f)) {
3818 set_accel_for_target_speed(objp, max_allowed_speed*dot*dot*dot);
3819 } else {
3820 if ((aip->path_cur - aip->path_start < aip->path_length-2) && (dist_to_goal < 2*objp->radius)) {
3821 set_accel_for_target_speed(objp, dist_to_goal/8.0f + 2.0f);
3822 } else {
3823 set_accel_for_target_speed(objp, max_allowed_speed * (2*xdot + 0.25f)/4.0f);
3824 }
3825 }
3826 }
3827 } else {
3828 float xdot;
3829
3830 xdot = MAX(dot_to_next, 0.1f);
3831 if ( aip->mode != AIM_DOCK ) {
3832 set_accel_for_target_speed(objp, max_allowed_speed);
3833 } else {
3834 float speed;
3835 if ((aip->path_cur - aip->path_start < aip->path_length-2) && (dist_to_goal < 2*objp->radius)) {
3836 speed = dist_to_goal/8.0f + 2.0f;
3837 } else if (dist_to_goal < 4*objp->radius + 50.0f) {
3838 speed = dist_to_goal/4.0f + 4.0f;
3839 } else {
3840 speed = max_allowed_speed * (3*xdot + 1.0f)/4.0f;
3841 }
3842 if (aip->mode == AIM_DOCK) {
3843 speed = speed * 2.0f + 1.0f;
3844 if (aip->goal_objnum != -1) {
3845 speed += Objects[aip->goal_objnum].phys_info.speed;
3846 }
3847 }
3848
3849 set_accel_for_target_speed(objp, speed);
3850 }
3851 }
3852 }
3853 }
3854
3855 // --------------------------------------------------------------------------
3856 // Follow a path associated with a large object, such as a capital ship.
3857 // The points defined on the path are in the object's reference frame.
3858 // The object of interest is goal_objnum.
3859 // The paths are defined in the model. The path of interest is wp_list.
3860 // The next goal point in the path is wp_index.
3861 // wp_flags contain special information specific to the path.
3862
3863 // The path vertices are defined by model_path structs:
3864 // typedef struct model_path {
3865 // char name[MAX_NAME_LEN]; // name of the subsystem. Probably displayed on HUD
3866 // int nverts;
3867 // vec3d *verts;
3868 // } model_path;
3869
3870 // The polymodel struct for the object contains the following:
3871 // int n_paths;
3872 // model_path *paths;
3873
3874 // Returns distance to goal point.
ai_path_0()3875 float ai_path_0()
3876 {
3877 int num_points;
3878 float dot, dist_to_goal, dist_to_next, dot_to_next;
3879 ship *shipp = &Ships[Pl_objp->instance];
3880 ship_info *sip = &Ship_info[shipp->ship_info_index];
3881 ai_info *aip;
3882 vec3d nvel_vec;
3883 float mag;
3884 vec3d temp_vec, *slop_vec;
3885 object *gobjp;
3886 vec3d *cvp, *nvp, next_vec, gcvp, gnvp; // current and next vertices in global coordinates.
3887
3888 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
3889
3890 Assert(aip->goal_objnum != -1);
3891 Assert(Objects[aip->goal_objnum].type == OBJ_SHIP);
3892
3893 gobjp = &Objects[aip->goal_objnum];
3894
3895 if (aip->path_start == -1) {
3896 Assert(aip->goal_objnum >= 0 && aip->goal_objnum < MAX_OBJECTS);
3897 int path_num;
3898 Assert(aip->active_goal >= 0);
3899 ai_goal *aigp = &aip->goals[aip->active_goal];
3900 Assert(aigp->flags[AI::Goal_Flags::Dockee_index_valid] && aigp->flags[AI::Goal_Flags::Docker_index_valid]);
3901
3902 path_num = ai_return_path_num_from_dockbay(&Objects[aip->goal_objnum], aigp->dockee.index);
3903 ai_find_path(Pl_objp, aip->goal_objnum, path_num, 0);
3904 }
3905
3906 maybe_recreate_path(Pl_objp, aip, 0);
3907
3908 num_points = aip->path_length;
3909
3910 // Set cvp and nvp as pointers to current and next vertices of interest on path.
3911 cvp = &Path_points[aip->path_cur].pos;
3912 if ((aip->path_cur + aip->path_dir - aip->path_start < num_points) || (aip->path_cur + aip->path_dir < aip->path_start))
3913 nvp = &Path_points[aip->path_cur + aip->path_dir].pos;
3914 else {
3915 // If this is 0, then path length must be 1 which means we have no direction!
3916 Assert((aip->path_cur - aip->path_dir >= aip->path_start) && (aip->path_cur - aip->path_dir - aip->path_start < num_points));
3917 // Cleanup for above Assert() which we hit too near release. -- MK, 5/24/98.
3918 if (aip->path_cur - aip->path_dir - aip->path_start >= num_points) {
3919 if (aip->path_dir == 1)
3920 aip->path_cur = aip->path_start;
3921 else
3922 aip->path_cur = aip->path_start + num_points - 1;
3923 }
3924
3925 vec3d delvec;
3926 vm_vec_sub(&delvec, cvp, &Path_points[aip->path_cur - aip->path_dir].pos);
3927 vm_vec_normalize(&delvec);
3928 vm_vec_scale_add(&next_vec, cvp, &delvec, 10.0f);
3929 nvp = &next_vec;
3930 }
3931
3932 // See if can reach next point (as opposed to current point)
3933 // However, don't do this if docking and next point is last point.
3934 // That is, we don't want to pursue the last point under control of the
3935 // path code. In docking, this is a special hack.
3936 if ((aip->mode != AIM_DOCK) || ((aip->path_cur-aip->path_start) < num_points - 2)) {
3937 if ((aip->path_cur + aip->path_dir > aip->path_start) && (aip->path_cur + aip->path_dir < aip->path_start + num_points-2)) {
3938 if ( timestamp_elapsed(aip->path_next_check_time)) {
3939 aip->path_next_check_time = timestamp( 3000 );
3940 if (!pp_collide(&Pl_objp->pos, nvp, gobjp, 1.1f * Pl_objp->radius)) {
3941 cvp = nvp;
3942 aip->path_cur += aip->path_dir;
3943 nvp = &Path_points[aip->path_cur].pos;
3944 }
3945 }
3946 }
3947 }
3948
3949 gcvp = *cvp;
3950 gnvp = *nvp;
3951
3952 dist_to_goal = vm_vec_dist_quick(&Pl_objp->pos, &gcvp);
3953 dist_to_next = vm_vec_dist_quick(&Pl_objp->pos, &gnvp);
3954 // Can't use fvec, need to use velocity vector because we aren't necessarily
3955 // moving in the direction we're facing.
3956
3957 if ( vm_vec_mag_quick(&Pl_objp->phys_info.vel) < AICODE_SMALL_MAGNITUDE ) {
3958 mag = 0.0f;
3959 vm_vec_zero(&nvel_vec);
3960 } else
3961 mag = vm_vec_copy_normalize(&nvel_vec, &Pl_objp->phys_info.vel);
3962
3963 // If moving not-very-slowly and sliding, then try to slide at goal, rather than
3964 // point at goal.
3965 slop_vec = NULL;
3966 if (mag < 1.0f)
3967 nvel_vec = Pl_objp->orient.vec.fvec;
3968 else if (mag > 5.0f) {
3969 float nv_dot;
3970 nv_dot = vm_vec_dot(&Pl_objp->orient.vec.fvec, &nvel_vec);
3971 if ((nv_dot > 0.5f) && (nv_dot < 0.97f)) {
3972 slop_vec = &temp_vec;
3973 vm_vec_sub(slop_vec, &nvel_vec, &Pl_objp->orient.vec.fvec);
3974 }
3975 }
3976
3977 if (dist_to_goal > 0.1f)
3978 ai_turn_towards_vector(&gcvp, Pl_objp, slop_vec, nullptr, 0.0f, 0);
3979
3980 // Code to control speed is MUCH less forgiving in path following than in waypoint
3981 // following. Must be very close to path or might hit objects.
3982 dot = vm_vec_dot_to_point(&nvel_vec, &Pl_objp->pos, &gcvp);
3983 dot_to_next = vm_vec_dot_to_point(&nvel_vec, &Pl_objp->pos, &gnvp);
3984
3985 set_accel_for_docking(Pl_objp, aip, dot, dot_to_next, dist_to_next, dist_to_goal, sip, 0, gobjp);
3986 aip->prev_dot_to_goal = dot;
3987
3988 // If moving at a non-tiny velocity, detect attaining path point by its being close to
3989 // line between previous and current object location.
3990 if ((dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || (vm_vec_dist_quick(&Pl_objp->last_pos, &Pl_objp->pos) > 0.1f)) {
3991 vec3d nearest_point;
3992 float r, min_dist_to_goal;
3993
3994 r = find_nearest_point_on_line(&nearest_point, &Pl_objp->last_pos, &Pl_objp->pos, &gcvp);
3995
3996 // Set min_dist_to_goal = how close must be to waypoint to pick next one.
3997 // If docking and this is the second last waypoint, must be very close.
3998 if ((aip->mode == AIM_DOCK) && (aip->path_cur >= aip->path_length-2))
3999 min_dist_to_goal = MIN_DIST_TO_WAYPOINT_GOAL;
4000 else
4001 min_dist_to_goal = MIN_DIST_TO_WAYPOINT_GOAL + Pl_objp->radius;
4002
4003 if ( (vm_vec_dist_quick(&Pl_objp->pos, &gcvp) < min_dist_to_goal) ||
4004 (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, &gcvp) < (MIN_DIST_TO_WAYPOINT_GOAL + Pl_objp->radius)))) {
4005 aip->path_cur += aip->path_dir;
4006 if (((aip->path_cur - aip->path_start) > (num_points+1)) || (aip->path_cur < aip->path_start)) {
4007 Assert(aip->mode != AIM_DOCK); // If docking, should never get this far, getting to last point handled outside ai_path()
4008 aip->path_dir = -aip->path_dir;
4009 }
4010 }
4011 }
4012
4013 return dist_to_goal;
4014 }
4015
4016 // --------------------------------------------------------------------------
4017 // Alternate version of ai_path
4018 // 1.
ai_path_1()4019 float ai_path_1()
4020 {
4021 int num_points;
4022 float dot, dist_to_goal, dist_to_next, dot_to_next;
4023 ship *shipp = &Ships[Pl_objp->instance];
4024 ship_info *sip = &Ship_info[shipp->ship_info_index];
4025 ai_info *aip;
4026 vec3d nvel_vec;
4027 float mag;
4028 object *gobjp;
4029 vec3d *pvp, *cvp, *nvp, next_vec, gpvp, gcvp, gnvp; // previous, current and next vertices in global coordinates.
4030
4031 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
4032
4033 Assert(aip->goal_objnum != -1);
4034 Assert(Objects[aip->goal_objnum].type == OBJ_SHIP);
4035
4036 gobjp = &Objects[aip->goal_objnum];
4037
4038 if (aip->path_start == -1) {
4039 Assert(aip->goal_objnum >= 0 && aip->goal_objnum < MAX_OBJECTS);
4040 int path_num;
4041 Assert(aip->active_goal >= 0);
4042 ai_goal *aigp = &aip->goals[aip->active_goal];
4043 Assert(aigp->flags[AI::Goal_Flags::Dockee_index_valid] && aigp->flags[AI::Goal_Flags::Docker_index_valid]);
4044
4045 path_num = ai_return_path_num_from_dockbay(&Objects[aip->goal_objnum], aigp->dockee.index);
4046 ai_find_path(Pl_objp, aip->goal_objnum, path_num, 0);
4047 }
4048
4049 maybe_recreate_path(Pl_objp, aip, 0);
4050
4051 num_points = aip->path_length;
4052
4053 // Set cvp and nvp as pointers to current and next vertices of interest on path.
4054 cvp = &Path_points[aip->path_cur].pos;
4055 if ((aip->path_cur + aip->path_dir - aip->path_start < num_points) || (aip->path_cur + aip->path_dir < aip->path_start))
4056 nvp = &Path_points[aip->path_cur + aip->path_dir].pos;
4057 else {
4058 // If this is 0, then path length must be 1 which means we have no direction!
4059 Assert((aip->path_cur - aip->path_dir >= aip->path_start) && (aip->path_cur - aip->path_dir - aip->path_start < num_points));
4060 // Cleanup for above Assert() which we hit too near release. -- MK, 5/24/98.
4061 if (aip->path_cur - aip->path_dir - aip->path_start >= num_points) {
4062 if (aip->path_dir == 1)
4063 aip->path_cur = aip->path_start;
4064 else
4065 aip->path_cur = aip->path_start + num_points - 1;
4066 }
4067
4068 vec3d delvec;
4069 vm_vec_sub(&delvec, cvp, &Path_points[aip->path_cur - aip->path_dir].pos);
4070 vm_vec_normalize(&delvec);
4071 vm_vec_scale_add(&next_vec, cvp, &delvec, 10.0f);
4072 nvp = &next_vec;
4073 }
4074 // Set pvp to the previous vertex of interest on the path (if there is one)
4075 int prev_point = aip->path_cur - aip->path_dir;
4076 if (prev_point >= aip->path_start && prev_point <= aip->path_start + num_points)
4077 pvp = &Path_points[prev_point].pos;
4078 else
4079 pvp = NULL;
4080
4081 gpvp = (pvp != NULL) ? *pvp : *cvp;
4082 gcvp = *cvp;
4083 gnvp = *nvp;
4084
4085 dist_to_goal = vm_vec_dist_quick(&Pl_objp->pos, &gcvp);
4086 dist_to_next = vm_vec_dist_quick(&Pl_objp->pos, &gnvp);
4087
4088 // Can't use fvec, need to use velocity vector because we aren't necessarily
4089 // moving in the direction we're facing.
4090 if ( vm_vec_mag_quick(&Pl_objp->phys_info.vel) < AICODE_SMALL_MAGNITUDE ) {
4091 mag = 0.0f;
4092 vm_vec_zero(&nvel_vec);
4093 } else
4094 mag = vm_vec_copy_normalize(&nvel_vec, &Pl_objp->phys_info.vel);
4095
4096 if (mag < 1.0f)
4097 nvel_vec = Pl_objp->orient.vec.fvec;
4098
4099 // For departures, optionally rotate so the ship is pointing the correct direction
4100 // (so you can always have "wheels-down" landings)
4101 vec3d rvec;
4102 vec3d *prvec = NULL;
4103 if ( aip->mode == AIM_BAY_DEPART && !IS_VEC_NULL(&aip->path_depart_orient) ) {
4104 vm_vec_unrotate(&rvec, &aip->path_depart_orient, &Objects[aip->path_objnum].orient);
4105 prvec = &rvec;
4106 }
4107
4108 // Turn toward a point between where we are on the path and the goal. This helps keep the ship flying
4109 // "down the line"
4110 if (dist_to_goal > 0.1f) {
4111 //Get nearest point on line between current and previous path points
4112 vec3d closest_point;
4113 float r = find_nearest_point_on_line(&closest_point, &gpvp, &gcvp, &Pl_objp->pos);
4114
4115 //Turn towards the 1/3 point between the closest point on the line and the current goal point
4116 //(unless we are already past the current goal)
4117 if (r <= 1.0f) {
4118 vec3d midpoint;
4119 vm_vec_avg3(&midpoint, &gcvp, &closest_point, &closest_point);
4120 ai_turn_towards_vector(&midpoint, Pl_objp, nullptr, nullptr, 0.0f, 0, prvec);
4121 }
4122 else {
4123 ai_turn_towards_vector(&gcvp, Pl_objp, nullptr, nullptr, 0.0f, 0, prvec);
4124 }
4125 }
4126
4127 // Code to control speed is MUCH less forgiving in path following than in waypoint
4128 // following. Must be very close to path or might hit objects.
4129 dot = vm_vec_dot_to_point(&nvel_vec, &Pl_objp->pos, &gcvp);
4130 dot_to_next = vm_vec_dot_to_point(&nvel_vec, &Pl_objp->pos, &gnvp);
4131
4132 // This path mode respects the "cap-waypoint-speed" SEXP
4133 float max_allowed_speed = 0.0f;
4134 if ( aip->waypoint_speed_cap > 0) {
4135 max_allowed_speed = (float) aip->waypoint_speed_cap;
4136 }
4137
4138 set_accel_for_docking(Pl_objp, aip, dot, dot_to_next, dist_to_next, dist_to_goal, sip, max_allowed_speed, gobjp);
4139 aip->prev_dot_to_goal = dot;
4140
4141 // If moving at a non-tiny velocity, detect attaining path point by its being close to
4142 // line between previous and current object location.
4143 if ((dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || (vm_vec_dist_quick(&Pl_objp->last_pos, &Pl_objp->pos) > 0.1f)) {
4144 vec3d nearest_point;
4145 float r, min_dist_to_goal;
4146
4147 r = find_nearest_point_on_line(&nearest_point, &Pl_objp->last_pos, &Pl_objp->pos, &gcvp);
4148
4149 // Set min_dist_to_goal = how close must be to waypoint to pick next one.
4150 // If docking and this is the second last waypoint, must be very close.
4151 if ((aip->mode == AIM_DOCK) && (aip->path_cur - aip->path_start >= aip->path_length-1)) {
4152 min_dist_to_goal = MIN_DIST_TO_WAYPOINT_GOAL;
4153 }
4154 // If this is not the last waypoint, include a term to compensate for inertia
4155 else if (aip->path_cur - aip->path_start < aip->path_length - (aip->mode == AIM_DOCK ? 0 : 1)) {
4156 vec3d cp_rel;
4157 vm_vec_sub(&cp_rel, &gcvp, &Pl_objp->pos);
4158 float goal_mag = 0.0f;
4159 if (vm_vec_mag(&cp_rel) > 0.0f) {
4160 vm_vec_normalize(&cp_rel);
4161 goal_mag = vm_vec_dot(&Pl_objp->phys_info.vel, &cp_rel);
4162 }
4163 min_dist_to_goal = MIN_DIST_TO_WAYPOINT_GOAL + Pl_objp->radius + (goal_mag * sip->damp);
4164 }
4165 else {
4166 min_dist_to_goal = MIN_DIST_TO_WAYPOINT_GOAL + Pl_objp->radius;
4167 }
4168
4169 if ( (vm_vec_dist_quick(&Pl_objp->pos, &gcvp) < min_dist_to_goal) ||
4170 (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, &gcvp) < (MIN_DIST_TO_WAYPOINT_GOAL + Pl_objp->radius)))) {
4171 aip->path_cur += aip->path_dir;
4172 if (((aip->path_cur - aip->path_start) > (num_points+1)) || (aip->path_cur < aip->path_start)) {
4173 Assert(aip->mode != AIM_DOCK); // If docking, should never get this far, getting to last point handled outside ai_path()
4174 aip->path_dir = -aip->path_dir;
4175 }
4176 }
4177 }
4178
4179 return dist_to_goal;
4180 }
4181
ai_path()4182 float ai_path()
4183 {
4184 switch (The_mission.ai_profile->ai_path_mode)
4185 {
4186 case AI_PATH_MODE_NORMAL:
4187 return ai_path_0();
4188 break;
4189 case AI_PATH_MODE_ALT1:
4190 return ai_path_1();
4191 break;
4192 default:
4193 Error(LOCATION, "Invalid path mode found: %d\n", The_mission.ai_profile->ai_path_mode);
4194 return ai_path_0();
4195 }
4196 }
4197
4198 // Only needed because CLAMP() doesn't handle pointers
update_min_max(float val,float * min,float * max)4199 void update_min_max(float val, float *min, float *max)
4200 {
4201 if (val < *min)
4202 *min = val;
4203 else if (val > *max)
4204 *max = val;
4205 }
4206
4207 // Stuff bounding box of all enemy objects within "range" units of object *my_objp.
4208 // Stuff ni min_vec and max_vec.
4209 // Return value: Number of enemy objects in bounding box.
get_enemy_team_range(object * my_objp,float range,int enemy_team_mask,vec3d * min_vec,vec3d * max_vec)4210 int get_enemy_team_range(object *my_objp, float range, int enemy_team_mask, vec3d *min_vec, vec3d *max_vec)
4211 {
4212 object *objp;
4213 ship_obj *so;
4214 int count = 0;
4215
4216 for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
4217 objp = &Objects[so->objnum];
4218 if (iff_matches_mask(Ships[objp->instance].team, enemy_team_mask)) {
4219 ship_info* sip = &Ship_info[Ships[objp->instance].ship_info_index];
4220 if (sip->is_fighter_bomber() || sip->flags[Ship::Info_Flags::Cruiser] || sip->flags[Ship::Info_Flags::Capital] || sip->flags[Ship::Info_Flags::Supercap] || sip->flags[Ship::Info_Flags::Drydock] || sip->flags[Ship::Info_Flags::Corvette] || sip->flags[Ship::Info_Flags::Awacs] || sip->flags[Ship::Info_Flags::Gas_miner])
4221 if (vm_vec_dist_quick(&my_objp->pos, &objp->pos) < range) {
4222 if (count == 0) {
4223 *min_vec = objp->pos;
4224 *max_vec = objp->pos;
4225 count++;
4226 }
4227 else {
4228 update_min_max(objp->pos.xyz.x, &min_vec->xyz.x, &max_vec->xyz.x);
4229 update_min_max(objp->pos.xyz.y, &min_vec->xyz.y, &max_vec->xyz.y);
4230 update_min_max(objp->pos.xyz.z, &min_vec->xyz.z, &max_vec->xyz.z);
4231 }
4232 }
4233
4234 }
4235 }
4236
4237 return count;
4238 }
4239
4240 // Pick a relatively safe spot for objp to fly to.
4241 // Problem:
4242 // Finds a spot away from any enemy within a bounding box.
4243 // Doesn't verify that "safe spot" is not near some other enemy.
ai_safety_pick_spot(object * objp)4244 void ai_safety_pick_spot(object *objp)
4245 {
4246 int objnum;
4247 int enemy_team_mask;
4248 vec3d min_vec, max_vec;
4249 vec3d vec_to_center, center;
4250 vec3d goal_pos;
4251
4252 objnum = OBJ_INDEX(objp);
4253
4254 enemy_team_mask = iff_get_attacker_mask(obj_team(&Objects[objnum]));
4255
4256 if (get_enemy_team_range(objp, 1000.0f, enemy_team_mask, &min_vec, &max_vec)) {
4257 vm_vec_avg(¢er, &min_vec, &max_vec);
4258 vm_vec_normalized_dir(&vec_to_center, ¢er, &objp->pos);
4259
4260 vm_vec_scale_add(&goal_pos, ¢er, &vec_to_center, 2000.0f);
4261 } else
4262 vm_vec_scale_add(&goal_pos, &objp->pos, &objp->orient.vec.fvec, 100.0f);
4263
4264 Ai_info[Ships[objp->instance].ai_index].goal_point = goal_pos;
4265 }
4266
4267 // Fly to desired safe point.
4268 // Returns distance to that point.
ai_safety_goto_spot(object * objp)4269 float ai_safety_goto_spot(object *objp)
4270 {
4271 float dot, dist;
4272 ai_info *aip;
4273 vec3d vec_to_goal;
4274 ship_info *sip;
4275 float dot_val;
4276
4277 sip = &Ship_info[Ships[objp->instance].ship_info_index];
4278
4279 aip = &Ai_info[Ships[objp->instance].ai_index];
4280 dist = vm_vec_normalized_dir(&vec_to_goal, &aip->goal_point, &objp->pos);
4281 dot = vm_vec_dot(&vec_to_goal, &objp->orient.vec.fvec);
4282
4283 dot_val = (1.1f + dot) / 2.0f;
4284 if (dist > 200.0f) {
4285 set_accel_for_target_speed(objp, sip->max_speed * dot_val);
4286 } else
4287 set_accel_for_target_speed(objp, sip->max_speed * dot_val * (dist/200.0f + 0.2f));
4288
4289 return dist;
4290 }
4291
ai_safety_circle_spot(object * objp)4292 void ai_safety_circle_spot(object *objp)
4293 {
4294 vec3d goal_point;
4295 ship_info *sip;
4296 float dot;
4297
4298 sip = &Ship_info[Ships[objp->instance].ship_info_index];
4299
4300 goal_point = Ai_info[Ships[objp->instance].ai_index].goal_point;
4301 dot = turn_towards_tangent(objp, &goal_point, 250.0f); // Increased from 50 to 250 to make circling not look so wacky.
4302
4303 set_accel_for_target_speed(objp, 0.5f * (1.0f + dot) * sip->max_speed/4.0f);
4304 }
4305
4306 // --------------------------------------------------------------------------
ai_safety()4307 void ai_safety()
4308 {
4309 ai_info *aip;
4310
4311 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
4312
4313 switch (aip->submode) {
4314 case AISS_1:
4315 ai_safety_pick_spot(Pl_objp);
4316 aip->submode = AISS_2;
4317 aip->submode_start_time = Missiontime;
4318 break;
4319 case AISS_1a: // Pick a safe point because we just got whacked!
4320 Int3();
4321 break;
4322 case AISS_2:
4323 if (ai_safety_goto_spot(Pl_objp) < 25.0f) {
4324 aip->submode = AISS_3;
4325 aip->submode_start_time = Missiontime;
4326 }
4327 break;
4328 case AISS_3:
4329 ai_safety_circle_spot(Pl_objp);
4330 break;
4331 default:
4332 Int3(); // Illegal submode for ai_safety();
4333 break;
4334 }
4335 }
4336
4337
4338 // --------------------------------------------------------------------------
4339 // make Pl_objp fly to its target's position
4340 // optionally returns true if Pl_objp (pl_done_p) has reached the target position (can be NULL)
4341 // optionally returns true if Pl_objp's (pl_treat_as_ship_p) fly to order was issued to him directly,
4342 // or if the order was issued to his wing (returns false) (can be NULL, and result is only
4343 // valid if Pl_done_p was not NULL and was set true by function.
ai_fly_to_target_position(vec3d * target_pos,bool * pl_done_p=NULL,bool * pl_treat_as_ship_p=NULL)4344 void ai_fly_to_target_position(vec3d* target_pos, bool* pl_done_p=NULL, bool* pl_treat_as_ship_p=NULL)
4345 {
4346 float dot, dist_to_goal;
4347 ship *shipp = &Ships[Pl_objp->instance];
4348 ship_info *sip = &Ship_info[shipp->ship_info_index];
4349 ai_info *aip;
4350 vec3d nvel_vec;
4351 float mag;
4352 float prev_dot_to_goal;
4353 vec3d temp_vec;
4354 vec3d *slop_vec;
4355 int j;
4356 bool ab_allowed = true;
4357
4358 Assert( target_pos != NULL );
4359
4360 if ( pl_done_p != NULL ) {
4361 // initialize this to false now incase we leave early so that that
4362 // the caller of this function doesn't get any funny ideas about
4363 // the status of the waypoint
4364 *pl_done_p = false;
4365 }
4366
4367 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
4368
4369 /* I shouldn't be flying to position for what ever called me any more.
4370 Set mode to none so that default dynamic behaviour gets started up again. */
4371 if ( (aip->active_goal == AI_GOAL_NONE) || (aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC) ) {
4372 aip->mode = AIM_NONE;
4373 }
4374
4375 dist_to_goal = vm_vec_dist_quick(&Pl_objp->pos, target_pos);
4376
4377 // Can't use fvec, need to use velocity vector because we aren't necessarily
4378 // moving in the direction we're facing.
4379 // AL 23-3-98: Account for very small velocities by checking result of vm_vec_mag().
4380 // If we don't vm_vec_copy_normalize() will think it is normalizing a null vector.
4381 if ( vm_vec_mag_quick(&Pl_objp->phys_info.vel) < AICODE_SMALL_MAGNITUDE ) {
4382 mag = 0.0f;
4383 vm_vec_zero(&nvel_vec);
4384 } else {
4385 mag = vm_vec_copy_normalize(&nvel_vec, &Pl_objp->phys_info.vel);
4386 }
4387
4388 // If moving not-very-slowly and sliding, then try to slide at goal, rather than
4389 // point at goal.
4390 slop_vec = NULL;
4391 if (mag < 1.0f) {
4392 nvel_vec = Pl_objp->orient.vec.fvec;
4393 } else if (mag > 5.0f) {
4394 float nv_dot;
4395 nv_dot = vm_vec_dot(&Pl_objp->orient.vec.fvec, &nvel_vec);
4396 if ((nv_dot > 0.5f) && (nv_dot < 0.97f)) {
4397 slop_vec = &temp_vec;
4398 vm_vec_sub(slop_vec, &nvel_vec, &Pl_objp->orient.vec.fvec);
4399 }
4400 }
4401
4402 // If a wing leader, take turns more slowly, based on size of wing.
4403 int scale;
4404
4405 if (aip->wing >= 0) {
4406 scale = Wings[aip->wing].current_count;
4407 scale = (int) ((scale+1)/2);
4408 } else {
4409 scale = 1;
4410 }
4411
4412 // ----------------------------------------------
4413 // if in autopilot mode make sure to not collide
4414 // and "keep reasonable distance"
4415 // this needs to be done for ALL SHIPS not just capships STOP CHANGING THIS
4416 // ----------------------------------------------
4417
4418 vec3d perp, goal_point;
4419
4420 bool carry_flag = ((shipp->flags[Ship::Ship_Flags::Navpoint_carry]) || ((shipp->wingnum >= 0) && (Wings[shipp->wingnum].flags[Ship::Wing_Flags::Nav_carry])));
4421
4422 float waypoint_turnrate = 1 / (3.0f * scale);
4423
4424 // for retail compatability reasons, have to take into account rotdamp so that ships with less than 1 rotdamp have increased waypoint turnrate
4425 // please see PR #2740 and #3494 for more information
4426 if (Pl_objp->phys_info.rotdamp < 1.0f )
4427 // start increasing the turnrate to 2x at 0.5 rotdamp and 3x at 0
4428 waypoint_turnrate *= (3 - 2 * Pl_objp->phys_info.rotdamp);
4429
4430 vec3d turnrate_mod;
4431 vm_vec_make(&turnrate_mod, waypoint_turnrate, waypoint_turnrate, waypoint_turnrate);
4432
4433 if (AutoPilotEngaged)
4434 ab_allowed = false;
4435
4436 if (AutoPilotEngaged
4437 && timestamp_elapsed(LockAPConv)
4438 && carry_flag
4439 && ((The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics]) || (Pl_objp != Autopilot_flight_leader)) )
4440 {
4441 Assertion( Autopilot_flight_leader != NULL, "When under autopilot there must be a flight leader" );
4442 // snap wings into formation them into formation
4443 if (The_mission.flags[Mission::Mission_Flags::Use_ap_cinematics]) {
4444 if (aip->wing != -1) {
4445 int wing_index = get_wing_index(Pl_objp, aip->wing);
4446 object *wing_leader = get_wing_leader(aip->wing);
4447
4448 if (wing_leader != Pl_objp) {
4449 // not wing leader.. get my position relative to wing leader
4450 get_absolute_wing_pos(&goal_point, wing_leader, aip->wing, wing_index, aip->ai_flags[AI::AI_Flags::Formation_object], true);
4451 } else {
4452 // Am wing leader.. get the wings position relative to the flight leader
4453 j = 1+int( (float)floor(double(autopilot_wings[aip->wing]-1)/2.0) );
4454
4455 switch (autopilot_wings[aip->wing] % 2) {
4456 case 1: // back-left
4457 vm_vec_copy_normalize(&perp, &Autopilot_flight_leader->orient.vec.rvec);
4458 vm_vec_scale(&perp, -166.0f*j); // 166m is supposedly the optimal range according to tolwyn
4459 vm_vec_add(&goal_point, &Autopilot_flight_leader->pos, &perp);
4460 break;
4461
4462 default: //back-right
4463 case 0:
4464 vm_vec_copy_normalize(&perp, &Autopilot_flight_leader->orient.vec.rvec);
4465 vm_vec_scale(&perp, 166.0f*j);
4466 vm_vec_add(&goal_point, &Autopilot_flight_leader->pos, &perp);
4467 break;
4468 }
4469
4470 }
4471
4472 Pl_objp->pos = goal_point;
4473 }
4474
4475 vm_vec_sub(&perp, Navs[CurrentNav].GetPosition(), &Autopilot_flight_leader->pos);
4476 vm_vector_2_matrix(&Pl_objp->orient, &perp, NULL, NULL);
4477 } else {
4478 vm_vec_scale_add(&perp, &Pl_objp->pos, &Autopilot_flight_leader->phys_info.vel, 1000.0f);
4479 ai_turn_towards_vector(&perp, Pl_objp, slop_vec, nullptr, 0.0f, 0, nullptr, &turnrate_mod);
4480 }
4481 } else {
4482 if (dist_to_goal > 0.1f) {
4483 ai_turn_towards_vector(target_pos, Pl_objp, slop_vec, nullptr, 0.0f, 0, nullptr, &turnrate_mod);
4484 }
4485 }
4486
4487 // ----------------------------------------------
4488
4489 prev_dot_to_goal = aip->prev_dot_to_goal;
4490 dot = vm_vec_dot_to_point(&nvel_vec, &Pl_objp->pos, target_pos);
4491 aip->prev_dot_to_goal = dot;
4492
4493 if (Pl_objp->phys_info.speed < 0.0f) {
4494 accelerate_ship(aip, 1.0f/32);
4495 ab_allowed = false;
4496 } else if (prev_dot_to_goal > dot+0.01f) {
4497 // We are further from pointing at our goal this frame than last frame, so slow down.
4498 set_accel_for_target_speed(Pl_objp, Pl_objp->phys_info.speed * 0.95f);
4499 ab_allowed = false;
4500 } else if (dist_to_goal < 100.0f) {
4501 float slew_dot = vm_vec_dot(&Pl_objp->orient.vec.fvec, &nvel_vec);
4502 if (fl_abs(slew_dot) < 0.9f) {
4503 accelerate_ship(aip, 0.0f);
4504 } else if (dot < 0.88f + 0.1f*(100.0f - dist_to_goal)/100.0f) {
4505 accelerate_ship(aip, 0.0f);
4506 } else {
4507 accelerate_ship(aip, 0.5f * dot * dot);
4508 }
4509 ab_allowed = false;
4510 } else {
4511 float dot1;
4512 if (dist_to_goal < 250.0f) {
4513 dot1 = dot*dot*dot; // Very important to be pointing towards goal when nearby. Note, cubing preserves sign.
4514 } else {
4515 if (dot > 0.0f) {
4516 dot1 = dot*dot;
4517 } else {
4518 dot1 = dot;
4519 }
4520 }
4521
4522 if (dist_to_goal > 100.0f + Pl_objp->radius * 2) {
4523 if (dot < 0.2f) {
4524 dot1 = 0.2f;
4525 }
4526 }
4527
4528 if (sip->is_small_ship()) {
4529 set_accel_for_target_speed(Pl_objp, dot1 * dist_to_goal/5.0f);
4530 } else {
4531 set_accel_for_target_speed(Pl_objp, dot1 * dist_to_goal/10.0f);
4532 }
4533 if (dot1 < 0.8f)
4534 ab_allowed = false;
4535 }
4536
4537 if (!(sip->flags[Ship::Info_Flags::Afterburner]) || (shipp->flags[Ship::Ship_Flags::Afterburner_locked]) || !(aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use])) {
4538 ab_allowed = false;
4539 }
4540
4541 // Make sure not travelling too fast for someone to keep up.
4542 float max_allowed_speed = 9999.9f;
4543 float max_allowed_ab_speed = 10000.1f;
4544 float self_ab_speed = 10000.0f;
4545
4546 if (shipp->wingnum != -1) {
4547 max_allowed_speed = 0.9f * get_wing_lowest_max_speed(Pl_objp);
4548 max_allowed_ab_speed = 0.95f * get_wing_lowest_av_ab_speed(Pl_objp);
4549 if (ab_allowed) {
4550 float self_ab_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level];
4551 self_ab_scale = sip->afterburner_recover_rate * self_ab_scale / (sip->afterburner_burn_rate + sip->afterburner_recover_rate * self_ab_scale);
4552 self_ab_speed = 0.95f * (self_ab_scale * (Pl_objp->phys_info.afterburner_max_vel.xyz.z - shipp->current_max_speed) + shipp->current_max_speed);
4553 }
4554 }
4555
4556 // check if waypoint speed cap is set and adjust max speed
4557 if (aip->waypoint_speed_cap > 0) {
4558 max_allowed_speed = (float) aip->waypoint_speed_cap;
4559 max_allowed_ab_speed = max_allowed_speed;
4560 }
4561
4562 if ((self_ab_speed < 5.0f) || (max_allowed_ab_speed < 5.0f)){ // || ((shipp->wingnum != -1) && (dist_to_goal / max_allowed_ab_speed < 5.0f))){
4563 ab_allowed = false;
4564 } else if (max_allowed_ab_speed < shipp->current_max_speed) {
4565 if (max_allowed_speed < max_allowed_ab_speed) {
4566 max_allowed_speed = max_allowed_ab_speed;
4567 } else {
4568 ab_allowed = false;
4569 }
4570 }
4571
4572
4573 if (ab_allowed) {
4574 if (self_ab_speed <= (1.001 * max_allowed_ab_speed)){
4575 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
4576 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
4577 if (percent_left > 30.0f) {
4578 afterburners_start(Pl_objp);
4579 aip->afterburner_stop_time = Missiontime + 5 * F1_0;
4580 }
4581 }
4582 } else {
4583 float switch_value;
4584 if ((self_ab_speed - max_allowed_ab_speed) > (0.2f * max_allowed_ab_speed)) {
4585 switch_value = 0.2f * max_allowed_ab_speed;
4586 } else {
4587 switch_value = self_ab_speed - max_allowed_ab_speed;
4588 }
4589 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
4590 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
4591 if ((Pl_objp->phys_info.speed < (max_allowed_ab_speed - switch_value)) && (percent_left > 30.0f)){
4592 afterburners_start(Pl_objp);
4593 aip->afterburner_stop_time = Missiontime + 5 * F1_0;
4594 }
4595 } else {
4596 if (Pl_objp->phys_info.speed > (max_allowed_ab_speed + 0.9 * switch_value)){
4597 afterburners_stop(Pl_objp);
4598 }
4599 }
4600 }
4601
4602 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
4603 if (aip->prev_accel * shipp->current_max_speed > max_allowed_ab_speed) {
4604 accelerate_ship(aip, max_allowed_ab_speed / shipp->current_max_speed);
4605 }
4606 }
4607 } else {
4608 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON ) {
4609 afterburners_stop(Pl_objp);
4610 }
4611 if (aip->prev_accel * shipp->current_max_speed > max_allowed_speed) {
4612 accelerate_ship(aip, max_allowed_speed / shipp->current_max_speed);
4613 }
4614 }
4615
4616 float dist_to_cover_this_frame = (Pl_objp->phys_info.speed * flFrametime);
4617 if ( (dist_to_goal < MIN_DIST_TO_WAYPOINT_GOAL) || dist_to_cover_this_frame > 0.1f ) {
4618 vec3d nearest_point;
4619 float r;
4620
4621 r = find_nearest_point_on_line(&nearest_point, &Pl_objp->last_pos, &Pl_objp->pos, target_pos);
4622
4623 if ( (dist_to_goal < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius) + dist_to_cover_this_frame))
4624 || (((r >= 0.0f) && (r <= 1.0f)) && (vm_vec_dist_quick(&nearest_point, target_pos) < (MIN_DIST_TO_WAYPOINT_GOAL + fl_sqrt(Pl_objp->radius)))))
4625 {
4626 int treat_as_ship;
4627
4628 // we must be careful when dealing with wings. A ship in a wing might be completing
4629 // a waypoint for for the entire wing, or it might be completing a goal for itself. If
4630 // for itself and in a wing, treat the completion as we would a ship
4631 treat_as_ship = 1;
4632 if ( Ships[Pl_objp->instance].wingnum != -1 ) {
4633 int type;
4634
4635 // protect array access from invalid indexes
4636 if ((aip->active_goal >= 0) && (aip->active_goal < MAX_AI_GOALS)) {
4637 type = aip->goals[aip->active_goal].type;
4638 if ( (type == AIG_TYPE_EVENT_WING) || (type == AIG_TYPE_PLAYER_WING) ) {
4639 treat_as_ship = 0;
4640 } else {
4641 treat_as_ship = 1;
4642 }
4643 }
4644 }
4645 // setup out parameters
4646 if ( pl_done_p != NULL ) {
4647 *pl_done_p = true;
4648 if ( pl_treat_as_ship_p != NULL ) {
4649 if ( treat_as_ship ) {
4650 *pl_treat_as_ship_p = true;
4651 } else {
4652 *pl_treat_as_ship_p = false;
4653 }
4654 }
4655 } else {
4656 Assertion( pl_treat_as_ship_p != NULL,
4657 "pl_done_p cannot be NULL while pl_treat_as_ship_p is not NULL" );
4658 }
4659 }
4660 }
4661 }
4662
4663 // --------------------------------------------------------------------------
4664 // make Pl_objp fly waypoints.
ai_waypoints()4665 void ai_waypoints()
4666 {
4667 ai_info *aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
4668
4669 // sanity checking for stuff that should never happen
4670 if (aip->wp_index == INVALID_WAYPOINT_POSITION) {
4671 if (aip->wp_list == NULL) {
4672 Warning(LOCATION, "Waypoints should have been assigned already!");
4673 ai_start_waypoints(Pl_objp, &Waypoint_lists.front(), WPF_REPEAT);
4674 } else {
4675 Warning(LOCATION, "Waypoints should have been started already!");
4676 ai_start_waypoints(Pl_objp, aip->wp_list, WPF_REPEAT);
4677 }
4678 }
4679
4680 Assert(!aip->wp_list->get_waypoints().empty()); // What? Is this zero? Probably never got initialized!
4681
4682 bool done, treat_as_ship;
4683 ai_fly_to_target_position(aip->wp_list->get_waypoints()[aip->wp_index].get_pos(), &done, &treat_as_ship);
4684
4685 if ( done ) {
4686 // go on to next waypoint in path
4687 ++aip->wp_index;
4688
4689 if ( aip->wp_index == aip->wp_list->get_waypoints().size() ) {
4690 // have reached the last waypoint. Do I repeat?
4691 if ( aip->wp_flags & WPF_REPEAT ) {
4692 // go back to the start.
4693 aip->wp_index = 0;
4694 } else {
4695 // stay on the last waypoint.
4696 --aip->wp_index;
4697
4698 // Log a message that the wing or ship reached his waypoint and
4699 // remove the goal from the AI goals of the ship pr wing, respectively.
4700 // Whether we should treat this as a ship or a wing is determined by
4701 // ai_fly_to_target_position when it marks the AI directive as complete
4702 if ( treat_as_ship ) {
4703 ai_mission_goal_complete( aip ); // this call should reset the AI mode
4704 mission_log_add_entry( LOG_WAYPOINTS_DONE, Ships[Pl_objp->instance].ship_name, aip->wp_list->get_name(), -1 );
4705 } else {
4706 ai_mission_wing_goal_complete( Ships[Pl_objp->instance].wingnum, &(aip->goals[aip->active_goal]) );
4707 mission_log_add_entry( LOG_WAYPOINTS_DONE, Wings[Ships[Pl_objp->instance].wingnum].name, aip->wp_list->get_name(), -1 );
4708 }
4709 // adds scripting hook for 'On Waypoints Done' --wookieejedi
4710 if (Script_system.IsActiveAction(CHA_ONWAYPOINTSDONE)) {
4711 Script_system.SetHookObject("Ship", &Objects[Ships[Pl_objp->instance].objnum]);
4712 Script_system.SetHookVar("Wing", 'o', scripting::api::l_Wing.Set(Ships[Pl_objp->instance].wingnum));
4713 Script_system.SetHookVar("Waypointlist", 'o', scripting::api::l_WaypointList.Set(scripting::api::waypointlist_h(aip->wp_list)));
4714 Script_system.RunCondition(CHA_ONWAYPOINTSDONE);
4715 Script_system.RemHookVars({"Ship", "Wing", "Waypointlist"});
4716 }
4717 }
4718 }
4719 }
4720 }
4721
4722 // --------------------------------------------------------------------------
4723 // make Pl_objp fly toward a ship
ai_fly_to_ship()4724 void ai_fly_to_ship()
4725 {
4726 ai_info *aip;
4727 object* target_p = NULL;
4728
4729 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
4730
4731 if ( aip->mode != AIM_FLY_TO_SHIP ) {
4732 Warning(LOCATION,
4733 "ai_fly_to_ship called for '%s' when ai_info.mode not equal to AIM_FLY_TO_SHIP. Is actually '%d'",
4734 Ships[Pl_objp->instance].ship_name,
4735 aip->mode);
4736 aip->mode = AIM_NONE;
4737 return;
4738 }
4739 if ( aip->active_goal < 0 || aip->active_goal >= MAX_AI_GOALS ) {
4740 Warning(LOCATION,
4741 "'%s' is trying to fly-to a ship without an active AI_GOAL\n\n"
4742 "Active ai mode is '%d'",
4743 Ships[Pl_objp->instance].ship_name,
4744 aip->active_goal);
4745 aip->mode = AIM_NONE;
4746 return;
4747 }
4748 Assert( aip->goals[aip->active_goal].target_name != NULL );
4749 if ( aip->goals[aip->active_goal].target_name[0] == '\0' ) {
4750 Warning(LOCATION, "'%s' is trying to fly-to-ship without a name for the ship", Ships[Pl_objp->instance].ship_name);
4751 aip->mode = AIM_NONE;
4752 ai_remove_ship_goal( aip, aip->active_goal ); // function sets aip->active_goal to NONE for me
4753 return;
4754 }
4755
4756 for (int j = 0; j < MAX_SHIPS; j++)
4757 {
4758 if (Ships[j].objnum != -1 && !stricmp(aip->goals[aip->active_goal].target_name, Ships[j].ship_name))
4759 {
4760 target_p = &Objects[Ships[j].objnum];
4761 break;
4762 }
4763 }
4764
4765 if ( target_p == NULL ) {
4766 #ifndef NDEBUG
4767 for (int i = 0; i < MAX_AI_GOALS; i++)
4768 {
4769 if (aip->mode == AIM_FLY_TO_SHIP || aip->goals[i].ai_mode == AI_GOAL_FLY_TO_SHIP)
4770 {
4771 mprintf(("Ship '%s' told to fly to target ship '%s'\n",
4772 Ships[Pl_objp->instance].ship_name,
4773 aip->goals[i].target_name));
4774 }
4775 }
4776 #endif
4777 Warning(LOCATION, "Ship '%s' told to fly to a ship but none of the ships it was told to fly to exist.\n"
4778 "See log before this message for list of ships set as fly-to tagets",
4779 Ships[Pl_objp->instance].ship_name);
4780 aip->mode = AIM_NONE;
4781 ai_remove_ship_goal( aip, aip->active_goal ); // function sets aip->active_goal to NONE for me
4782 return;
4783 } else {
4784 bool done, treat_as_ship;
4785 ai_fly_to_target_position(&(target_p->pos), &done, &treat_as_ship);
4786
4787 if ( done ) {
4788 // remove the goal from the AI goals of the ship pr wing, respectively.
4789 // Wether or not we should treat this as a ship or a wing is determined by
4790 // ai_fly_to_target when it marks the AI directive as complete
4791 if ( treat_as_ship ) {
4792 ai_mission_goal_complete( aip ); // this call should reset the AI mode
4793 } else {
4794 ai_mission_wing_goal_complete( Ships[Pl_objp->instance].wingnum, &(aip->goals[aip->active_goal]) );
4795 }
4796 }
4797 }
4798 }
4799
4800 // Make Pl_objp avoid En_objp
4801 // Not like evading. This is for avoiding a collision!
4802 // Note, use sliding if available.
avoid_ship()4803 void avoid_ship()
4804 {
4805 // To avoid an object, turn towards right or left vector until facing away from object.
4806 // To choose right vs. left, pick one that is further from center of avoid object.
4807 // Keep turning away from until pointing away from ship.
4808 // Stay in avoid mode until at least 3 enemy ship radii away.
4809
4810 // Speed setting:
4811 // If inside sphere, zero speed and turn towards outside.
4812 // If outside sphere, inside 2x sphere, set speed percent of max to:
4813 // max(away_dot, (dist-rad)/rad)
4814 // where away_dot is dot(Pl_objp->fvec, vec_En_objp_to_Pl_objp)
4815
4816 vec3d vec_to_enemy;
4817 float away_dot;
4818 float dist;
4819 ship *shipp = &Ships[Pl_objp->instance];
4820 ship_info *sip = &Ship_info[shipp->ship_info_index];
4821 ai_info *aip = &Ai_info[shipp->ai_index];
4822 vec3d player_pos, enemy_pos;
4823
4824 // if we're avoiding a stealth ship, then we know where he is, update with no error
4825 if ( is_object_stealth_ship(En_objp) ) {
4826 update_ai_stealth_info_with_error(aip/*, 1*/);
4827 }
4828
4829 ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
4830 vm_vec_sub(&vec_to_enemy, &enemy_pos, &Pl_objp->pos);
4831
4832 dist = vm_vec_normalize(&vec_to_enemy);
4833 away_dot = -vm_vec_dot(&Pl_objp->orient.vec.fvec, &vec_to_enemy);
4834
4835 if ((sip->max_vel.xyz.x > 0.0f) || (sip->max_vel.xyz.y > 0.0f)) {
4836 if (vm_vec_dot(&Pl_objp->orient.vec.rvec, &vec_to_enemy) > 0.0f) {
4837 AI_ci.sideways = -1.0f;
4838 } else {
4839 AI_ci.sideways = 1.0f;
4840 }
4841 if (vm_vec_dot(&Pl_objp->orient.vec.uvec, &vec_to_enemy) > 0.0f) {
4842 AI_ci.vertical = -1.0f;
4843 } else {
4844 AI_ci.vertical = 1.0f;
4845 }
4846 }
4847
4848 // If in front of enemy, turn away from it.
4849 // If behind enemy, try to get fully behind it.
4850 if (away_dot < 0.0f) {
4851 turn_away_from_point(Pl_objp, &enemy_pos, Pl_objp->phys_info.speed);
4852 } else {
4853 vec3d goal_pos;
4854
4855 vm_vec_scale_add(&goal_pos, &En_objp->pos, &En_objp->orient.vec.fvec, -100.0f);
4856 turn_towards_point(Pl_objp, &goal_pos, NULL, Pl_objp->phys_info.speed);
4857 }
4858
4859 // Set speed.
4860 float radsum = Pl_objp->radius + En_objp->radius;
4861
4862 if (dist < radsum)
4863 accelerate_ship(aip, MAX(away_dot, 0.2f));
4864 else if (dist < 2*radsum)
4865 accelerate_ship(aip, MAX(away_dot, (dist - radsum) / radsum)+0.2f);
4866 else
4867 accelerate_ship(aip, 1.0f);
4868
4869 }
4870
4871 // Maybe it's time to resume the previous AI mode in aip->previous_mode.
4872 // Each type of previous_mode has its own criteria on when to resume.
4873 // Return true if previous mode was resumed.
maybe_resume_previous_mode(object * objp,ai_info * aip)4874 int maybe_resume_previous_mode(object *objp, ai_info *aip)
4875 {
4876 // Only (maybe) resume previous goal if current goal is dynamic.
4877 if (aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC)
4878 return 0;
4879
4880 if (aip->mode == AIM_EVADE_WEAPON) {
4881 if (timestamp_elapsed(aip->mode_time) || (((aip->nearest_locked_object == -1) || (Objects[aip->nearest_locked_object].type != OBJ_WEAPON)) && (aip->danger_weapon_objnum == -1))) {
4882 Assert(aip->previous_mode != AIM_EVADE_WEAPON);
4883 aip->mode = aip->previous_mode;
4884 aip->submode = aip->previous_submode;
4885 aip->submode_start_time = Missiontime;
4886 aip->active_goal = AI_GOAL_NONE;
4887 aip->mode_time = -1; // Means do forever.
4888 return 1;
4889 }
4890 } else if ( aip->previous_mode == AIM_GUARD) {
4891 if ((aip->guard_objnum != -1) && (aip->guard_signature == Objects[aip->guard_objnum].signature)) {
4892 object *guard_objp;
4893 float dist;
4894
4895 guard_objp = &Objects[aip->guard_objnum];
4896 dist = vm_vec_dist_quick(&guard_objp->pos, &objp->pos);
4897
4898 // If guarding ship is far away from guardee and enemy is far away from guardee,
4899 // then stop chasing and resume guarding.
4900 if (dist > (MAX_GUARD_DIST + guard_objp->radius) * 6) {
4901 if ((En_objp != NULL) && (En_objp->type == OBJ_SHIP)) {
4902 if (vm_vec_dist_quick(&guard_objp->pos, &En_objp->pos) > (MAX_GUARD_DIST + guard_objp->radius) * 6) {
4903 Assert(aip->previous_mode == AIM_GUARD);
4904 aip->mode = aip->previous_mode;
4905 aip->submode = AIS_GUARD_PATROL;
4906 aip->submode_start_time = Missiontime;
4907 aip->active_goal = AI_GOAL_NONE;
4908 return 1;
4909 }
4910 }
4911 }
4912 }
4913 }
4914
4915 return 0;
4916
4917 }
4918
4919 // Call this function if you want something to happen on average every N quarters of a second.
4920 // The truth value returned by this function will be the same for any given quarter second interval.
4921 // The value "num" is only passed in to get asynchronous behavior for different objects.
4922 // modulus == 1 will always return true.
4923 // modulus == 2 will return true half the time.
4924 // modulus == 16 will return true for one quarter second interval every four seconds.
static_rand_timed(int num,int modulus)4925 int static_rand_timed(int num, int modulus)
4926 {
4927 if (modulus < 2)
4928 return 1;
4929 else {
4930 int t;
4931
4932 t = Missiontime >> 18; // Get time in quarters of a second (SUSHI: this comment is wrong! It's actually every 4 seconds!)
4933 t += num;
4934
4935 return !(t % modulus);
4936 }
4937 }
4938
4939 /**
4940 * Maybe fire afterburner based on AI class
4941 */
ai_maybe_fire_afterburner(object * objp,ai_info * aip)4942 int ai_maybe_fire_afterburner(object *objp, ai_info *aip)
4943 {
4944 // bail if the ship doesn't even have an afterburner
4945 if (!(Ship_info[Ships[objp->instance].ship_info_index].flags[Ship::Info_Flags::Afterburner])) {
4946 return 0;
4947 }
4948 if (aip->ai_aburn_use_factor == INT_MIN && aip->ai_class == 0) {
4949 return 0; // Lowest level never aburners away (unless ai_aburn_use_factor is specified)
4950 }
4951 else {
4952 // Maybe don't afterburner because of a potential collision with the player.
4953 // If not multiplayer, near player and player in front, probably don't afterburner.
4954 if (!(Game_mode & GM_MULTIPLAYER)) {
4955 if (Ships[objp->instance].team == Player_ship->team) {
4956 float dist;
4957
4958 dist = vm_vec_dist_quick(&objp->pos, &Player_obj->pos) - Player_obj->radius - objp->radius;
4959 if (dist < 150.0f) {
4960 vec3d v2p;
4961 float dot;
4962
4963 vm_vec_normalized_dir(&v2p, &Player_obj->pos, &objp->pos);
4964 dot = vm_vec_dot(&v2p, &objp->orient.vec.fvec);
4965
4966 if (dot > 0.0f) {
4967 if (dot * dist > 50.0f)
4968 return 0;
4969 }
4970 }
4971 }
4972 }
4973
4974 if (aip->ai_aburn_use_factor == INT_MIN && aip->ai_class >= Num_ai_classes-2)
4975 return 1; // Highest two levels always aburner away (unless ai_aburn_use_factor is specified).
4976 else {
4977 //If ai_aburn_use_factor is not specified, calculate a number based on the AI class. Otherwise, use that value.
4978 if (aip->ai_aburn_use_factor == INT_MIN)
4979 return static_rand_timed(OBJ_INDEX(objp), Num_ai_classes - aip->ai_class);
4980 else
4981 return static_rand_timed(OBJ_INDEX(objp), aip->ai_aburn_use_factor);
4982 }
4983 }
4984 }
4985
4986 /**
4987 * Maybe engage afterburner after being hit by an object.
4988 */
maybe_afterburner_after_ship_hit(object * objp,ai_info * aip,object * en_objp)4989 void maybe_afterburner_after_ship_hit(object *objp, ai_info *aip, object *en_objp)
4990 {
4991 // Only do if facing a little away.
4992 if (en_objp != NULL) {
4993 vec3d v2e;
4994
4995 vm_vec_normalized_dir(&v2e, &en_objp->pos, &objp->pos);
4996 if (vm_vec_dot(&v2e, &objp->orient.vec.fvec) > -0.5f)
4997 return;
4998 }
4999
5000 if (!( objp->phys_info.flags & PF_AFTERBURNER_ON )) {
5001 if (ai_maybe_fire_afterburner(objp, aip)) {
5002 afterburners_start(objp);
5003 aip->afterburner_stop_time = Missiontime + F1_0/2;
5004 }
5005 }
5006 }
5007
5008 /**
5009 * Is an instructor if name begins INSTRUCTOR_SHIP_NAME else not.
5010 * @return true if object *objp is an instructor.
5011 */
is_instructor(object * objp)5012 int is_instructor(object *objp)
5013 {
5014 return !strnicmp(Ships[objp->instance].ship_name, INSTRUCTOR_SHIP_NAME, strlen(INSTRUCTOR_SHIP_NAME));
5015 }
5016
5017 // Evade the weapon aip->danger_weapon_objnum
5018 // If it's not valid, do a quick out.
5019 // Evade by accelerating hard.
5020 // If necessary, turn hard left or hard right.
evade_weapon()5021 void evade_weapon()
5022 {
5023 object *weapon_objp = NULL;
5024 object *unlocked_weapon_objp = NULL, *locked_weapon_objp = NULL;
5025 vec3d weapon_pos, player_pos, goal_point;
5026 vec3d vec_from_enemy;
5027 float dot_from_enemy, dot_to_enemy;
5028 float dist;
5029 ship *shipp = &Ships[Pl_objp->instance];
5030 ai_info *aip = &Ai_info[shipp->ai_index];
5031
5032 if (is_instructor(Pl_objp))
5033 return;
5034
5035 // Make sure we're actually being attacked.
5036 // Favor locked objects.
5037 if (aip->nearest_locked_object != -1) {
5038 if (Objects[aip->nearest_locked_object].type == OBJ_WEAPON)
5039 locked_weapon_objp = &Objects[aip->nearest_locked_object];
5040 }
5041
5042 if (aip->danger_weapon_objnum != -1) {
5043 if (Objects[aip->danger_weapon_objnum].signature == aip->danger_weapon_signature)
5044 unlocked_weapon_objp = &Objects[aip->danger_weapon_objnum];
5045 else
5046 aip->danger_weapon_objnum = -1; // Signatures don't match, so no longer endangered.
5047 }
5048
5049 if (locked_weapon_objp != NULL) {
5050 if (unlocked_weapon_objp != NULL) {
5051 if (vm_vec_dist_quick(&locked_weapon_objp->pos, &Pl_objp->pos) < 1.5f * vm_vec_dist_quick(&unlocked_weapon_objp->pos, &Pl_objp->pos))
5052 weapon_objp = locked_weapon_objp;
5053 else
5054 weapon_objp = unlocked_weapon_objp;
5055 } else
5056 weapon_objp = locked_weapon_objp;
5057 } else if (unlocked_weapon_objp != NULL)
5058 weapon_objp = unlocked_weapon_objp;
5059 else {
5060 if (aip->mode == AIM_EVADE_WEAPON)
5061 maybe_resume_previous_mode(Pl_objp, aip);
5062 return;
5063 }
5064
5065 Assert(weapon_objp != NULL);
5066
5067 if (weapon_objp->type != OBJ_WEAPON) {
5068 if (aip->mode == AIM_EVADE_WEAPON)
5069 maybe_resume_previous_mode(Pl_objp, aip);
5070 return;
5071 }
5072
5073 weapon_pos = weapon_objp->pos;
5074 player_pos = Pl_objp->pos;
5075
5076 // Make speed based on skill level, varying at highest skill level, which is harder to hit.
5077 accelerate_ship(aip, 1.0f);
5078
5079 dist = vm_vec_normalized_dir(&vec_from_enemy, &player_pos, &weapon_pos);
5080
5081 dot_to_enemy = -vm_vec_dot(&Pl_objp->orient.vec.fvec, &vec_from_enemy);
5082 dot_from_enemy = vm_vec_dot(&weapon_objp->orient.vec.fvec, &vec_from_enemy);
5083
5084 // If shot is incoming...
5085 if (dot_from_enemy < 0.3f) {
5086 if (weapon_objp == unlocked_weapon_objp)
5087 aip->danger_weapon_objnum = -1;
5088 return;
5089 } else if (dot_from_enemy > 0.7f) {
5090 bool should_afterburn = dist < 200.0f;
5091 int aburn_time = F1_0 / 2;
5092
5093 // within 200m bad, within 1.5 seconds good
5094 if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]) {
5095 should_afterburn = dist < weapon_objp->phys_info.speed * 1.5f;
5096 aburn_time = F1_0 + F1_0 / 2;
5097 }
5098
5099 if (should_afterburn) {
5100 if (!( Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
5101 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
5102 afterburners_start(Pl_objp);
5103 aip->afterburner_stop_time = Missiontime + aburn_time;
5104 }
5105 }
5106 }
5107
5108 // Fancy (read: effective) dodging
5109 // Try to go perpindicular to the incoming missile, and pick the quickest direction to get there
5110 if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]) {
5111 vec3d avoid_point;
5112 vm_project_point_onto_plane(&avoid_point, &vec_from_enemy, &Pl_objp->orient.vec.fvec, &vmd_zero_vector);
5113 vm_vec_normalize(&avoid_point);
5114 if (vm_vec_dot(&Pl_objp->orient.vec.fvec, &vec_from_enemy) > 0.f)
5115 vm_vec_negate(&avoid_point);
5116 vm_vec_scale_add(&avoid_point, &Pl_objp->pos, &avoid_point, 200.0f);
5117
5118 turn_towards_point(Pl_objp, &avoid_point, nullptr, 0.0f);
5119 }
5120 // If we're sort of pointing towards it...
5121 else if ((dot_to_enemy < -0.5f) || (dot_to_enemy > 0.5f)) {
5122 float rdot;
5123 float udot;
5124
5125 // Turn hard left or right, depending on which gets out of way quicker.
5126 //SUSHI: Also possibly turn up or down.
5127 rdot = vm_vec_dot(&Pl_objp->orient.vec.rvec, &vec_from_enemy);
5128 udot = vm_vec_dot(&Pl_objp->orient.vec.uvec, &vec_from_enemy);
5129
5130 if (aip->ai_profile_flags[AI::Profile_Flags::Allow_vertical_dodge] && fl_abs(udot) > fl_abs(rdot))
5131 {
5132 if ((udot < -0.5f) || (udot > 0.5f))
5133 vm_vec_scale_add(&goal_point, &Pl_objp->pos, &Pl_objp->orient.vec.uvec, -200.0f);
5134 else
5135 vm_vec_scale_add(&goal_point, &Pl_objp->pos, &Pl_objp->orient.vec.uvec, 200.0f);
5136 }
5137 else
5138 {
5139 if ((rdot < -0.5f) || (rdot > 0.5f))
5140 vm_vec_scale_add(&goal_point, &Pl_objp->pos, &Pl_objp->orient.vec.rvec, -200.0f);
5141 else
5142 vm_vec_scale_add(&goal_point, &Pl_objp->pos, &Pl_objp->orient.vec.rvec, 200.0f);
5143 }
5144
5145 turn_towards_point(Pl_objp, &goal_point, NULL, 0.0f);
5146 }
5147 }
5148
5149 }
5150
5151 // Use sliding and backwards moving to face enemy.
5152 // (Coded 2/20/98. Works fine, but it's hard to see how to integrate it into the AI system.
5153 // Typically ships are moving so fast that a little sliding isn't enough to gain an advantage.
5154 // It's currently used to avoid collisions and could be used to evade weapon fire, but the latter
5155 // would be frustrating, I think.
5156 // This function is currently not called.)
slide_face_ship()5157 void slide_face_ship()
5158 {
5159 ship_info *sip;
5160
5161 sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
5162
5163 // If can't slide, return.
5164 if ((sip->max_vel.xyz.x == 0.0f) && (sip->max_vel.xyz.y == 0.0f))
5165 return;
5166
5167 vec3d goal_pos;
5168 float dot_from_enemy;
5169 vec3d vec_from_enemy, vec_to_goal;
5170 float dist;
5171 float up, right;
5172 ai_info *aip;
5173
5174 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
5175
5176 dist = vm_vec_normalized_dir(&vec_from_enemy, &Pl_objp->pos, &En_objp->pos);
5177
5178 ai_turn_towards_vector(&En_objp->pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
5179
5180 dot_from_enemy = vm_vec_dot(&vec_from_enemy, &En_objp->orient.vec.fvec);
5181
5182 if (vm_vec_dot(&vec_from_enemy, &En_objp->orient.vec.rvec) > 0.0f)
5183 right = 1.0f;
5184 else
5185 right = -1.0f;
5186
5187 if (vm_vec_dot(&vec_from_enemy, &En_objp->orient.vec.uvec) > 0.0f)
5188 up = 1.0f;
5189 else
5190 up = -1.0f;
5191
5192 vm_vec_scale_add(&goal_pos, &En_objp->pos, &En_objp->orient.vec.rvec, right * 200.0f);
5193 vm_vec_scale_add(&goal_pos, &En_objp->pos, &En_objp->orient.vec.uvec, up * 200.0f);
5194
5195 vm_vec_normalized_dir(&vec_to_goal, &goal_pos, &Pl_objp->pos);
5196
5197 if (vm_vec_dot(&vec_to_goal, &Pl_objp->orient.vec.rvec) > 0.0f)
5198 AI_ci.sideways = 1.0f;
5199 else
5200 AI_ci.sideways = -1.0f;
5201
5202 if (vm_vec_dot(&vec_to_goal, &Pl_objp->orient.vec.uvec) > 0.0f)
5203 AI_ci.vertical = 1.0f;
5204 else
5205 AI_ci.vertical = -1.0f;
5206
5207 if (dist < 200.0f) {
5208 if (dot_from_enemy < 0.7f)
5209 accelerate_ship(aip, -1.0f);
5210 else
5211 accelerate_ship(aip, dot_from_enemy + 0.5f);
5212 } else {
5213 if (dot_from_enemy < 0.7f) {
5214 accelerate_ship(aip, 0.2f);
5215 } else {
5216 accelerate_ship(aip, 1.0f);
5217 }
5218 }
5219 }
5220
5221 // General code for handling one ship evading another.
5222 // Problem: This code is also used for avoiding an impending collision.
5223 // In such a case, it is not good to go to max speed, which is often good
5224 // for a certain kind of evasion.
evade_ship()5225 void evade_ship()
5226 {
5227 vec3d player_pos, enemy_pos, goal_point;
5228 vec3d vec_from_enemy;
5229 float dot_from_enemy;
5230 float dist;
5231 ship *shipp = &Ships[Pl_objp->instance];
5232 ship_info *sip = &Ship_info[shipp->ship_info_index];
5233 ai_info *aip = &Ai_info[shipp->ai_index];
5234 float bank_override = 0.0f;
5235
5236 ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
5237
5238 // Make speed based on skill level, varying at highest skill level, which is harder to hit.
5239 if (Game_skill_level == NUM_SKILL_LEVELS-1) {
5240 int rand_int;
5241 float accel_val;
5242
5243 rand_int = static_rand(OBJ_INDEX(Pl_objp));
5244 accel_val = (float) (((Missiontime^rand_int) >> 14) & 0x0f)/32.0f + 0.5f;
5245 accelerate_ship(aip, accel_val);
5246 } else
5247 accelerate_ship(aip, (float) (Game_skill_level+2) / (NUM_SKILL_LEVELS+1));
5248
5249 if ((Missiontime - aip->submode_start_time > F1_0/2) && (sip->afterburner_fuel_capacity > 0.0f)) {
5250 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
5251 if (percent_left > 30.0f + ((OBJ_INDEX(Pl_objp)) & 0x0f)) {
5252 afterburners_start(Pl_objp);
5253
5254 if (aip->ai_profile_flags[AI::Profile_Flags::Smart_afterburner_management]) {
5255 aip->afterburner_stop_time = (fix) (Missiontime + F1_0 + static_randf(OBJ_INDEX(Pl_objp)) * F1_0 / 4);
5256 } else {
5257 aip->afterburner_stop_time = Missiontime + F1_0 + static_rand(OBJ_INDEX(Pl_objp))/4;
5258 }
5259 }
5260 }
5261
5262 vm_vec_sub(&vec_from_enemy, &player_pos, &enemy_pos);
5263
5264 dist = vm_vec_normalize(&vec_from_enemy);
5265 dot_from_enemy = vm_vec_dot(&En_objp->orient.vec.fvec, &vec_from_enemy);
5266
5267 if (dist > 250.0f) {
5268 vec3d gp1, gp2;
5269 // If far away from enemy, circle, going to nearer of point far off left or right wing
5270 vm_vec_scale_add(&gp1, &enemy_pos, &En_objp->orient.vec.rvec, 250.0f);
5271 vm_vec_scale_add(&gp2, &enemy_pos, &En_objp->orient.vec.rvec, -250.0f);
5272 if (vm_vec_dist_quick(&gp1, &Pl_objp->pos) < vm_vec_dist_quick(&gp2, &Pl_objp->pos))
5273 goal_point = gp1;
5274 else
5275 goal_point = gp2;
5276 } else if (dot_from_enemy < 0.1f) {
5277 // If already close to behind, goal is to get completely behind.
5278 vm_vec_scale_add(&goal_point, &enemy_pos, &En_objp->orient.vec.fvec, -1000.0f);
5279 } else if (dot_from_enemy > 0.9f) {
5280 // If enemy pointing almost right at self, and self pointing close to enemy, turn away from
5281 vec3d vec_to_enemy;
5282 float dot_to_enemy;
5283
5284 vm_vec_sub(&vec_to_enemy, &enemy_pos, &player_pos);
5285
5286 vm_vec_normalize(&vec_to_enemy);
5287 dot_to_enemy = vm_vec_dot(&Pl_objp->orient.vec.fvec, &vec_to_enemy);
5288 if (dot_to_enemy > 0.75f) {
5289 // Used to go to En_objp's right vector, but due to banking while turning, that
5290 // caused flying in an odd spiral.
5291 vm_vec_scale_add(&goal_point, &enemy_pos, &Pl_objp->orient.vec.rvec, 1000.0f);
5292 if (dist < 100.0f)
5293 bank_override = Pl_objp->phys_info.speed;
5294 } else {
5295 bank_override = Pl_objp->phys_info.speed; // In enemy's sights, not pointing at him, twirl away.
5296 goto evade_ship_l1;
5297 }
5298 } else {
5299 evade_ship_l1: ;
5300 if (aip->ai_evasion > Random::next()*Random::INV_F_MAX_VALUE*100.0f) {
5301 int temp;
5302 float scale;
5303 float psrandval; // some value close to zero to choose whether to turn right or left.
5304
5305 psrandval = (float) (((Missiontime >> 14) & 0x0f) - 8); // Value between -8 and 7
5306 psrandval = psrandval/16.0f; // Value between -1/2 and 1/2 (approx)
5307
5308 // If not close to behind, turn towards his right or left vector, whichever won't cross his path.
5309 if (vm_vec_dot(&vec_from_enemy, &En_objp->orient.vec.rvec) > psrandval) {
5310 scale = 1000.0f;
5311 } else {
5312 scale = -1000.0f;
5313 }
5314
5315 vm_vec_scale_add(&goal_point, &enemy_pos, &En_objp->orient.vec.rvec, scale);
5316
5317 temp = ((Missiontime >> 16) & 0x07);
5318 temp = ((temp * (temp+1)) % 16)/2 - 4;
5319 if ((psrandval == 0) && (temp == 0))
5320 temp = 3;
5321
5322 scale = 200.0f * temp;
5323
5324 vm_vec_scale_add2(&goal_point, &En_objp->orient.vec.uvec, scale);
5325 } else {
5326 // No evasion this frame, but continue with previous turn.
5327 // Reason: If you don't, you lose rotational momentum. Turning every other frame,
5328 // and not in between results in a very slow turn because of loss of momentum.
5329 if ((aip->prev_goal_point.xyz.x != 0.0f) || (aip->prev_goal_point.xyz.y != 0.0f) || (aip->prev_goal_point.xyz.z != 0.0f))
5330 goal_point = aip->prev_goal_point;
5331 else
5332 vm_vec_scale_add(&goal_point, &enemy_pos, &En_objp->orient.vec.rvec, 100.0f);
5333 }
5334 }
5335
5336 turn_towards_point(Pl_objp, &goal_point, NULL, bank_override);
5337
5338 aip->prev_goal_point = goal_point;
5339 }
5340
5341 // --------------------------------------------------------------------------
5342 // Fly in a manner making it difficult for opponent to attack.
ai_evade()5343 void ai_evade()
5344 {
5345 evade_ship();
5346 }
5347
5348
5349 float G_collision_time;
5350 vec3d G_predicted_pos, G_fire_pos;
5351
5352
5353 //old version of this fuction, this will be useful for playing old missions and not having the new primary
5354 //selection code throw off the balance of the mission.
5355 // If:
5356 // flags & Weapon::Info_Flags::Puncture
5357 // Then Select a Puncture weapon.
5358 // Else
5359 // Select Any ol' weapon.
5360 // Returns primary_bank index.
ai_select_primary_weapon_OLD(const object * objp,Weapon::Info_Flags flags)5361 static int ai_select_primary_weapon_OLD(const object *objp, Weapon::Info_Flags flags)
5362 {
5363 ship *shipp = &Ships[objp->instance];
5364 ship_weapon *swp = &shipp->weapons;
5365
5366 Assert( shipp->ship_info_index >= 0 && shipp->ship_info_index < ship_info_size());
5367
5368 if (flags == Weapon::Info_Flags::Puncture) {
5369 if (swp->current_primary_bank >= 0) {
5370 int bank_index;
5371
5372 bank_index = swp->current_primary_bank;
5373
5374 if (Weapon_info[swp->primary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::Puncture]) {
5375 return swp->current_primary_bank;
5376 }
5377 }
5378 for (int i=0; i<swp->num_primary_banks; i++) {
5379 int weapon_info_index;
5380
5381 weapon_info_index = swp->primary_bank_weapons[i];
5382
5383 if (weapon_info_index > -1){
5384 if (Weapon_info[weapon_info_index].wi_flags[Weapon::Info_Flags::Puncture]) {
5385 swp->current_primary_bank = i;
5386 return i;
5387 }
5388 }
5389 }
5390
5391 // AL 26-3-98: If we couldn't find a puncture weapon, pick first available weapon if one isn't active
5392 if ( swp->current_primary_bank < 0 ) {
5393 if ( swp->num_primary_banks > 0 ) {
5394 swp->current_primary_bank = 0;
5395 }
5396 }
5397
5398 } else { // Don't need to be using a puncture weapon.
5399 if (swp->current_primary_bank >= 0) {
5400 if (!(Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].wi_flags[Weapon::Info_Flags::Puncture])){
5401 return swp->current_primary_bank;
5402 }
5403 }
5404 for (int i=0; i<swp->num_primary_banks; i++) {
5405 if (swp->primary_bank_weapons[i] > -1) {
5406 if (!(Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Puncture])) {
5407 swp->current_primary_bank = i;
5408 nprintf(("AI", "%i: Ship %s selecting weapon %s\n", Framecount, Ships[objp->instance].ship_name, Weapon_info[swp->primary_bank_weapons[i]].name));
5409 return i;
5410 }
5411 }
5412 }
5413 // Wasn't able to find a non-puncture weapon. Stick with what we have.
5414 }
5415
5416 Assert( swp->current_primary_bank != -1 ); // get Alan or Allender
5417
5418 return swp->current_primary_bank;
5419 }
5420
5421 // If:
5422 // flags == Weapon::Info_Flags::Puncture
5423 // Then Select a Puncture weapon.
5424 // Else
5425 // Select Any ol' weapon.
5426 // Returns primary_bank index.
5427 /**
5428 * Etc. Etc. This is like the 4th rewrite of the code here. Special thanks to Bobboau
5429 * for finding the get_shield_strength function.
5430 *
5431 * The AI will now intelligently choose the best weapon to use based on the overall shield
5432 * status of the target.
5433 */
ai_select_primary_weapon(object * objp,object * other_objp,Weapon::Info_Flags flags)5434 int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags)
5435 {
5436 // Pointer Set Up
5437 ship *shipp = &Ships[objp->instance];
5438 ship_weapon *swp = &shipp->weapons;
5439
5440 // Debugging
5441 if (other_objp==NULL)
5442 {
5443 // this can be NULL in the case of a target death and attacker weapon
5444 // change. using notification message instead of a fault
5445 mprintf(("'other_objpp == NULL' in ai_select_primary_weapon()\n"));
5446 return -1;
5447 }
5448
5449 //not using the new AI, use the old version of this function instead.
5450 if (!(Ai_info[shipp->ai_index].ai_profile_flags[AI::Profile_Flags::Smart_primary_weapon_selection]))
5451 {
5452 return ai_select_primary_weapon_OLD(objp, flags);
5453 }
5454
5455 Assert( shipp->ship_info_index >= 0 && shipp->ship_info_index < ship_info_size());
5456
5457 //made it so it only selects puncture weapons if the active goal is to disable something -Bobboau
5458 if ((flags == Weapon::Info_Flags::Puncture) && (Ai_info[shipp->ai_index].goals[0].ai_mode & (AI_GOAL_DISARM_SHIP | AI_GOAL_DISABLE_SHIP)))
5459 {
5460 if (swp->current_primary_bank >= 0)
5461 {
5462 int bank_index = swp->current_primary_bank;
5463
5464 if (Weapon_info[swp->primary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::Puncture])
5465 {
5466 return swp->current_primary_bank;
5467 }
5468 }
5469 for (int i=0; i<swp->num_primary_banks; i++)
5470 {
5471 int weapon_info_index = swp->primary_bank_weapons[i];
5472
5473 if (weapon_info_index >= 0)
5474 {
5475 if (Weapon_info[weapon_info_index].wi_flags[Weapon::Info_Flags::Puncture])
5476 {
5477 swp->current_primary_bank = i;
5478 return i;
5479 }
5480 }
5481 }
5482 }
5483
5484 bool other_is_ship = (other_objp->type == OBJ_SHIP);
5485 float enemy_remaining_shield = get_shield_pct(other_objp);
5486
5487 if ( other_is_ship )
5488 {
5489 ship_info* other_sip = &Ship_info[Ships[other_objp->instance].ship_info_index];
5490
5491 if (other_sip->class_type >= 0 && Ship_types[other_sip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only]) {
5492 //Check if we have a capital+ weapon on board
5493 for (int i = 0; i < swp->num_primary_banks; i++)
5494 {
5495 if (swp->primary_bank_weapons[i] >= 0) // Make sure there is a weapon in the bank
5496 {
5497 auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
5498 if (wip->wi_flags[Weapon::Info_Flags::Capital_plus])
5499 {
5500 // handle shielded big/huge ships (non FS-verse)
5501 // only pick capital+ if the target has no shields; or the weapon is an ok shield-breaker anyway
5502 if (enemy_remaining_shield <= 0.05f || (wip->shield_factor * 1.33f > wip->armor_factor))
5503 {
5504 swp->current_primary_bank = i;
5505 nprintf(("AI", "%i: Ship %s selecting weapon %s (capital+) vs target %s\n", Framecount, Ships[objp->instance].ship_name, wip->name, Ships[other_objp->instance].ship_name));
5506 return i;
5507 }
5508 }
5509 }
5510 }
5511 }
5512 }
5513
5514 // Is the target shielded by say only 5%?
5515 if (enemy_remaining_shield <= 0.05f)
5516 {
5517 // Then it is safe to start using a heavy hull damage weapon such as the maxim
5518 float i_hullfactor_prev = 0; // Previous weapon bank hull factor (this is the safe way to do it)
5519 int i_hullfactor_prev_bank = -1; // Bank which gave us this hull factor
5520
5521 // Find the weapon with the highest hull * damage / fire delay factor
5522 for (int i = 0; i < swp->num_primary_banks; i++)
5523 {
5524 if (swp->primary_bank_weapons[i] >= 0) // Make sure there is a weapon in the bank
5525 {
5526 auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
5527 if ((((wip->armor_factor) * (wip->damage)) / wip->fire_wait) > i_hullfactor_prev)
5528 {
5529 if ( !(wip->wi_flags[Weapon::Info_Flags::Capital_plus]) )
5530 {
5531 // This weapon is the new candidate
5532 i_hullfactor_prev = ( ((wip->armor_factor) * (wip->damage)) / wip->fire_wait );
5533 i_hullfactor_prev_bank = i;
5534 }
5535 }
5536 }
5537 }
5538 if (i_hullfactor_prev_bank == -1) // In the unlikely instance we don't find at least 1 candidate weapon
5539 {
5540 i_hullfactor_prev_bank = 0; // Just switch to the first one
5541 }
5542 swp->current_primary_bank = i_hullfactor_prev_bank; // Select the best weapon
5543 nprintf(("AI", "%i: Ship %s selecting weapon %s (no shields) vs target %s\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_hullfactor_prev_bank]].name, (other_is_ship ? Ships[other_objp->instance].ship_name : "non-ship") ));
5544 return i_hullfactor_prev_bank; // Return
5545 }
5546
5547 //if the shields are above lets say 10% definitely use a pierceing weapon if there are any-Bobboau
5548 if (enemy_remaining_shield >= 0.10f)
5549 {
5550 if (swp->current_primary_bank >= 0)
5551 {
5552 auto wip = &Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]];
5553
5554 if ((wip->wi_flags[Weapon::Info_Flags::Pierce_shields]) && !(wip->wi_flags[Weapon::Info_Flags::Capital_plus]))
5555 {
5556 nprintf(("AI", "%i: Ship %s selecting weapon %s (>10%% shields)\n", Framecount, shipp->ship_name, wip->name));
5557 return swp->current_primary_bank;
5558 }
5559 }
5560 for (int i=0; i<swp->num_primary_banks; i++)
5561 {
5562 if (swp->primary_bank_weapons[i] >= 0)
5563 {
5564 auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
5565 if ((wip->wi_flags[Weapon::Info_Flags::Pierce_shields]) && !(wip->wi_flags[Weapon::Info_Flags::Capital_plus]))
5566 {
5567 swp->current_primary_bank = i;
5568 nprintf(("AI", "%i: Ship %s selecting weapon %s (>10%% shields)\n", Framecount, shipp->ship_name, wip->name));
5569 return i;
5570 }
5571 }
5572 }
5573 }
5574
5575 // Is the target shielded by less then 50%?
5576 if (enemy_remaining_shield <= 0.50f)
5577 {
5578 // Should be using best balanced shield/hull gun
5579 float i_balancedfactor_prev = 0; // Previous weapon bank "balanced" factor (this is the safe way to do it)
5580 int i_balancedfactor_prev_bank = -1; // Bank which gave us this "balanced" factor
5581 // Find the weapon with the highest average hull and shield * damage / fire delay factor
5582 for (int i = 0; i < swp->num_primary_banks; i++)
5583 {
5584 if (swp->primary_bank_weapons[i] >= 0) // Make sure there is a weapon in the bank
5585 {
5586 auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
5587 if ((((wip->armor_factor + wip->shield_factor) * wip->damage) / wip->fire_wait) > i_balancedfactor_prev)
5588 {
5589 if ( !(wip->wi_flags[Weapon::Info_Flags::Capital_plus]) )
5590 {
5591 // This weapon is the new candidate
5592 i_balancedfactor_prev = ((((wip->armor_factor + wip->shield_factor) * wip->damage) / wip->fire_wait));
5593 i_balancedfactor_prev_bank = i;
5594 }
5595 }
5596 }
5597 }
5598 if (i_balancedfactor_prev_bank == -1) // In the unlikely instance we don't find at least 1 candidate weapon
5599 {
5600 i_balancedfactor_prev_bank = 0; // Just switch to the first one
5601 }
5602 swp->current_primary_bank = i_balancedfactor_prev_bank; // Select the best weapon
5603 nprintf(("AI", "%i: Ship %s selecting weapon %s (<50%% shields)\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_balancedfactor_prev_bank]].name));
5604 return i_balancedfactor_prev_bank; // Return
5605 }
5606 else
5607 {
5608 // Should be using best shield destroying gun
5609 float i_shieldfactor_prev = 0; // Previous weapon bank shield factor (this is the safe way to do it)
5610 int i_shieldfactor_prev_bank = -1; // Bank which gave us this shield factor
5611 // Find the weapon with the highest average hull and shield * damage / fire delay factor
5612 for (int i = 0; i < swp->num_primary_banks; i++)
5613 {
5614 if (swp->primary_bank_weapons[i] >= 0) // Make sure there is a weapon in the bank
5615 {
5616 auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
5617 if ((((wip->shield_factor) * wip->damage) / wip->fire_wait) > i_shieldfactor_prev)
5618 {
5619 if ( !(wip->wi_flags[Weapon::Info_Flags::Capital_plus]) )
5620 {
5621 // This weapon is the new candidate
5622 i_shieldfactor_prev = ( ((wip->shield_factor) * (wip->damage)) / wip->fire_wait );
5623 i_shieldfactor_prev_bank = i;
5624 }
5625 }
5626 }
5627 }
5628 if (i_shieldfactor_prev_bank == -1) // In the unlikely instance we don't find at least 1 candidate weapon
5629 {
5630 i_shieldfactor_prev_bank = 0; // Just switch to the first one
5631 }
5632 swp->current_primary_bank = i_shieldfactor_prev_bank; // Select the best weapon
5633 nprintf(("AI", "%i: Ship %s selecting weapon %s (>50%% shields)\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_shieldfactor_prev_bank]].name));
5634 return i_shieldfactor_prev_bank; // Return
5635 }
5636 }
5637
5638 /**
5639 * Maybe link primary weapons.
5640 */
set_primary_weapon_linkage(object * objp)5641 void set_primary_weapon_linkage(object *objp)
5642 {
5643 ship *shipp;
5644 ship_info *sip;
5645 ai_info *aip;
5646 ship_weapon *swp;
5647 weapon_info *wip;
5648
5649 shipp = &Ships[objp->instance];
5650
5651 if (Num_weapons > (int) (MAX_WEAPONS * 0.75f)) {
5652 if (shipp->flags[Ship::Ship_Flags::Primary_linked]) {
5653 nprintf(("AI", "Frame %i, ship %s: Unlinking primaries.\n", Framecount, shipp->ship_name));
5654 shipp->flags.remove(Ship::Ship_Flags::Primary_linked);
5655 }
5656 return; // If low on slots, don't link.
5657 }
5658
5659 sip = &Ship_info[shipp->ship_info_index];
5660 aip = &Ai_info[shipp->ai_index];
5661 swp = &shipp->weapons;
5662
5663 shipp->flags.remove(Ship::Ship_Flags::Primary_linked);
5664
5665 // AL: ensure target is a ship!
5666 if ( (aip->target_objnum != -1) && (Objects[aip->target_objnum].type == OBJ_SHIP) ) {
5667 // If trying to destroy a big ship (i.e., not disable/disarm), always unleash all weapons
5668 if ( Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index].is_big_ship() ) {
5669 if ( aip->targeted_subsys == nullptr ) {
5670 if (!sip->flags[Ship::Info_Flags::No_primary_linking] ) {
5671 shipp->flags.set(Ship::Ship_Flags::Primary_linked);
5672 }
5673 shipp->flags.set(Ship::Ship_Flags::Secondary_dual_fire);
5674 return;
5675 }
5676 }
5677 }
5678
5679 // AL 2-11-98: If ship has a disarm or disable goal, don't link unless both weapons are
5680 // puncture weapons
5681 if ( (aip->active_goal != AI_GOAL_NONE) && (aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) )
5682 {
5683 if ( aip->goals[aip->active_goal].ai_mode & (AI_GOAL_DISABLE_SHIP|AI_GOAL_DISARM_SHIP) )
5684 {
5685 // only continue if both primaries are puncture weapons
5686 if ( swp->num_primary_banks == 2 ) {
5687 if ( !(Weapon_info[swp->primary_bank_weapons[0]].wi_flags[Weapon::Info_Flags::Puncture]) )
5688 return;
5689 if ( !(Weapon_info[swp->primary_bank_weapons[1]].wi_flags[Weapon::Info_Flags::Puncture]) )
5690 return;
5691 }
5692 }
5693 }
5694
5695 // Don't want all ships always linking weapons at start, so asynchronize.
5696 if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Allow_primary_link_at_start]))
5697 {
5698 if (Missiontime < i2f(30))
5699 return;
5700 else if (Missiontime < i2f(120))
5701 {
5702 int r = static_rand((Missiontime >> 17) ^ OBJ_INDEX(objp));
5703 if ( (r&3) != 0)
5704 return;
5705 }
5706 }
5707
5708 // get energy level
5709 float energy;
5710 if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_linked_primary_bug]) {
5711 energy = shipp->weapon_energy / sip->max_weapon_reserve * 100.0f;
5712 } else {
5713 energy = shipp->weapon_energy;
5714 }
5715
5716 // make linking decision based on weapon energy
5717 // but only if primary linking is allowed --wookieejedi
5718 if (!sip->flags[Ship::Info_Flags::No_primary_linking]) {
5719 if (energy > aip->ai_link_energy_levels_always) {
5720 shipp->flags.set(Ship::Ship_Flags::Primary_linked);
5721 } else if (energy > aip->ai_link_ammo_levels_maybe) {
5722 if (objp->hull_strength < shipp->ship_max_hull_strength / 3.0f) {
5723 shipp->flags.set(Ship::Ship_Flags::Primary_linked);
5724 }
5725 }
5726 }
5727
5728 // also check ballistics - Goober5000
5729 int total_ammo = 0;
5730 int current_ammo = 0;
5731 bool all_ballistic = true;
5732
5733 // count ammo, and do not continue unless all weapons are ballistic
5734 for (int i = 0; i < swp->num_primary_banks; i++)
5735 {
5736 wip = &Weapon_info[swp->primary_bank_weapons[i]];
5737
5738 if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
5739 {
5740 total_ammo += swp->primary_bank_start_ammo[i];
5741 current_ammo += swp->primary_bank_ammo[i];
5742 }
5743 else
5744 {
5745 all_ballistic = false;
5746 break;
5747 }
5748 }
5749
5750 if (all_ballistic)
5751 {
5752 Assert(total_ammo); // Goober5000: div-0 check
5753 float ammo_pct = float (current_ammo) / float (total_ammo) * 100.0f;
5754
5755 // link according to defined levels
5756 if (ammo_pct > aip->ai_link_ammo_levels_always)
5757 {
5758 shipp->flags.set(Ship::Ship_Flags::Primary_linked);
5759 }
5760 else if (ammo_pct > aip->ai_link_ammo_levels_maybe)
5761 {
5762 if (objp->hull_strength < shipp->ship_max_hull_strength / 3.0f)
5763 {
5764 shipp->flags.set(Ship::Ship_Flags::Primary_linked);
5765 }
5766 }
5767 }
5768 }
5769
5770 const int MULTILOCK_CHECK_INTERVAL = 250; // every quarter of a second
5771
5772 // This function returns if the ship could theoretically have fully locked all available targets
5773 // given the current time it's been locking on its primary target
5774 // If so, it cheats a little bit and assumes that's what it has done, and fills
5775 // the ai's missile_locks_firing vector, and to be fired at those targets
ai_do_multilock(ai_info * aip,weapon_info * wip)5776 bool ai_do_multilock(ai_info* aip, weapon_info* wip) {
5777
5778 if (!timestamp_elapsed(aip->multilock_check_timestamp))
5779 return false;
5780
5781 object* primary_target = &Objects[aip->target_objnum];
5782
5783 struct multilock_target {
5784 object* objp;
5785 ship_subsys* subsys;
5786 float dot;
5787 };
5788
5789 // First we accrue all available targets into this vector which we will later sort by dot
5790 SCP_vector<multilock_target> multilock_targets;
5791
5792 if (wip->target_restrict == LR_CURRENT_TARGET)
5793 multilock_targets.push_back(multilock_target{ primary_target, aip->targeted_subsys, 1.0f });
5794 else if (wip->target_restrict == LR_CURRENT_TARGET_SUBSYS) {
5795
5796 if (primary_target->type != OBJ_SHIP)
5797 multilock_targets.push_back(multilock_target{ primary_target, nullptr, 1.0f });
5798 else {
5799 ship* sp = &Ships[primary_target->instance];
5800 ship_subsys* ss;
5801
5802 if (Ship_info[sp->ship_info_index].is_big_or_huge()) {
5803 for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss)) {
5804 float dot;
5805
5806 if (!weapon_multilock_can_lock_on_subsys(Pl_objp, primary_target, ss, wip, &dot))
5807 continue;
5808
5809 // this will guarantee the ship will send always send some missiles at its actual target
5810 if (aip->targeted_subsys == ss)
5811 dot = 1.0f;
5812
5813 multilock_targets.push_back(multilock_target{ primary_target, ss, dot });
5814 }
5815 } else
5816 multilock_targets.push_back(multilock_target{ primary_target, nullptr, 1.0f });
5817 }
5818 } else { // any target
5819 for (ship_obj* so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
5820 object* target_objp = &Objects[so->objnum];
5821 ship* target_ship = &Ships[target_objp->instance];
5822 float dot;
5823
5824 if (!weapon_multilock_can_lock_on_target(Pl_objp, target_objp, wip, &dot))
5825 continue;
5826
5827 if (Ship_info[target_ship->ship_info_index].is_big_or_huge()) {
5828 // add ALL the valid subsystems as possible targets
5829 // dont check range or lock fov, the sub function will do that for subsystems
5830 ship_subsys* ss;
5831 for (ss = GET_FIRST(&target_ship->subsys_list); ss != END_OF_LIST(&target_ship->subsys_list); ss = GET_NEXT(ss)) {
5832
5833 if (!weapon_multilock_can_lock_on_subsys(Pl_objp, target_objp, ss, wip, &dot))
5834 continue;
5835
5836 // this will guarantee the ship will send always send some missiles at its actual target
5837 if (aip->targeted_subsys == ss)
5838 dot = 1.0f;
5839
5840 multilock_targets.push_back(multilock_target{ target_objp, ss, dot });
5841 }
5842 } else {
5843 // just a small target, now we check range and fov
5844 if (!weapon_secondary_world_pos_in_range(Player_obj, wip, &target_objp->pos))
5845 continue;
5846
5847 if (dot < wip->lock_fov)
5848 continue;
5849
5850 // this will guarantee the ship will send always send some missiles at its actual target
5851 if (primary_target == target_objp)
5852 dot = 1.0f;
5853
5854 multilock_targets.push_back(multilock_target{ target_objp, nullptr, dot });
5855 }
5856 }
5857 }
5858
5859 // before any sorting let's first determine if we're maximally locked
5860 int allocatable_missiles = MIN(weapon_get_max_missile_seekers(wip), (int)multilock_targets.size() * wip->max_seekers_per_target);
5861 float missiles_allocated = wip->max_seeking * (aip->aspect_locked_time / wip->min_lock_time);
5862
5863 // we could still lock more missiles! wait for it (or couldn't find any targets??)
5864 if (missiles_allocated < allocatable_missiles || allocatable_missiles == 0) {
5865 aip->multilock_check_timestamp = timestamp(MULTILOCK_CHECK_INTERVAL);
5866 return false;
5867 }
5868
5869 // we're fully locked! sort by dot and let them fly!
5870 std::sort(multilock_targets.begin(), multilock_targets.end(), [](multilock_target &a, multilock_target &b)
5871 { return a.dot < b.dot; });
5872
5873 aip->ai_missile_locks_firing.clear();
5874 int missiles = weapon_get_max_missile_seekers(wip);
5875 int remaining_seekers_per_target = wip->max_seekers_per_target;
5876 for (size_t i = multilock_targets.size() - 1;; i--) {
5877 std::pair<int, ship_subsys*> lock;
5878
5879 lock.first = OBJ_INDEX(multilock_targets.at(i).objp);
5880 lock.second = multilock_targets.at(i).subsys;
5881 aip->ai_missile_locks_firing.push_back(lock);
5882 missiles--;
5883
5884 // out of missiles, we're done
5885 if (missiles == 0)
5886 break;
5887
5888 // still more missiles, but exhausted our targets, time to loop over them all again
5889 // and start double-locking triple-locking etc
5890 if (i == 0) {
5891 remaining_seekers_per_target--;
5892 // exhausted our seekers per target, we're done
5893 if (remaining_seekers_per_target == 0)
5894 break;
5895
5896 // reset i and go through the whole list again
5897 i = multilock_targets.size();
5898 }
5899 }
5900
5901 return true;
5902 }
5903
5904 // --------------------------------------------------------------------------
5905 // Fire the current primary weapon.
5906 // *objp is the object to fire from.
5907 //I changed this to return a true false flag on weather
5908 //it did or did not fire the weapon sorry if this screws you up-Bobboau
ai_fire_primary_weapon(object * objp)5909 int ai_fire_primary_weapon(object *objp)
5910 {
5911 ship *shipp = &Ships[objp->instance];
5912 ship_weapon *swp = &shipp->weapons;
5913 ship_info *enemy_sip;
5914 ai_info *aip;
5915 object *enemy_objp;
5916
5917 Assert( shipp->ship_info_index >= 0 && shipp->ship_info_index < ship_info_size());
5918
5919 aip = &Ai_info[shipp->ai_index];
5920
5921 // If low on slots, fire a little less often.
5922 if (Num_weapons > (int) (0.9f * MAX_WEAPONS)) {
5923 if (frand() > 0.5f) {
5924 nprintf(("AI", "Frame %i, %s not fire.\n", Framecount, shipp->ship_name));
5925 return 0;
5926 }
5927 }
5928
5929 if (!Ai_firing_enabled){
5930 return 0;
5931 }
5932
5933 if (aip->target_objnum != -1){
5934 enemy_objp = &Objects[aip->target_objnum];
5935 enemy_sip = (enemy_objp->type == OBJ_SHIP) ? &Ship_info[Ships[enemy_objp->instance].ship_info_index] : NULL;
5936 } else {
5937 enemy_objp = NULL;
5938 enemy_sip = NULL;
5939 }
5940
5941 if ( (swp->current_primary_bank < 0) || (swp->current_primary_bank >= swp->num_primary_banks) || timestamp_elapsed(aip->primary_select_timestamp)) {
5942 Weapon::Info_Flags flags = Weapon::Info_Flags::NUM_VALUES;
5943 if ( aip->targeted_subsys != NULL ) {
5944 flags = Weapon::Info_Flags::Puncture;
5945 }
5946 ai_select_primary_weapon(objp, enemy_objp, flags);
5947 ship_primary_changed(shipp); // AL: maybe send multiplayer information when AI ship changes primaries
5948 aip->primary_select_timestamp = timestamp(5 * 1000); // Maybe change primary weapon five seconds from now.
5949 }
5950
5951 //We can only check LoS if we have a target defined
5952 weapon_info* wip = &Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]];
5953 if (aip->target_objnum != -1 && (The_mission.ai_profile->flags[AI::Profile_Flags::Require_exact_los] || wip->wi_flags[Weapon::Info_Flags::Require_exact_los])) {
5954 //Check if th AI has Line of Sight and is allowed to fire
5955 if (!check_los(OBJ_INDEX(objp), aip->target_objnum, 10.0f, swp->current_primary_bank, -1, nullptr)) {
5956 return 0;
5957 }
5958 }
5959
5960 // If pointing nearly at predicted collision point of target, bash orientation to be perfectly pointing.
5961 float dot;
5962 vec3d v2t;
5963
5964 bool condition;
5965 if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ramming_stationary_targets_bug]) {
5966 // fixed by Mantis 3147 - Avoid bashing orientation if trying to avoid a collision.
5967 condition = !(vm_vec_mag_quick(&G_predicted_pos) < AICODE_SMALL_MAGNITUDE) && (aip->submode != SM_AVOID);
5968 } else {
5969 // retail behavior
5970 condition = !(vm_vec_mag_quick(&G_predicted_pos) < AICODE_SMALL_MAGNITUDE);
5971 }
5972
5973 if ( condition ) {
5974 if ( vm_vec_cmp(&G_predicted_pos, &G_fire_pos) ) {
5975 vm_vec_normalized_dir(&v2t, &G_predicted_pos, &G_fire_pos);
5976 dot = vm_vec_dot(&v2t, &objp->orient.vec.fvec);
5977 if (dot > .998629534f){ // if within 3.0 degrees of desired heading, bash
5978 vm_vector_2_matrix(&objp->orient, &v2t, &objp->orient.vec.uvec, NULL);
5979 }
5980 }
5981 }
5982
5983 //SUSHI: Burst-fire for ballistic primaries.
5984 if (The_mission.ai_profile->primary_ammo_burst_mult[Game_skill_level] > 0 && //Make sure we are using burst fire
5985 enemy_objp != NULL && enemy_sip != NULL && //We need a target, obviously
5986 (enemy_objp->phys_info.speed >= 1.0f) && //Only burst for moving ships
5987 (enemy_sip->is_small_ship() || enemy_sip->flags[Ship::Info_Flags::Transport]) && //Only burst for small ships (transports count)
5988 swp->primary_bank_start_ammo[swp->current_primary_bank] > 0 && //Prevent div by 0
5989 Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].wi_flags[Weapon::Info_Flags::Ballistic]) //Current weapon must be ballistic
5990 {
5991 float percentAmmoLeft = ((float)swp->primary_bank_ammo[swp->current_primary_bank] / (float)swp->primary_bank_start_ammo[swp->current_primary_bank]);
5992 float distToTarget = vm_vec_dist(&enemy_objp->pos, &objp->pos);
5993 float weaponRange = Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].weapon_range;
5994 float distanceFactor = 1.0f - distToTarget/weaponRange;
5995 vec3d vecToTarget;
5996 vm_vec_normalized_dir(&vecToTarget, &enemy_objp->pos, &objp->pos);
5997 float dotToTarget = vm_vec_dot(&vecToTarget, &objp->orient.vec.fvec);
5998 //This makes the dot a tiny bit more impactful (otherwise nearly always over 0.98 or so)
5999 //square twice = raise to the 4th power
6000 dotToTarget *= dotToTarget;
6001 dotToTarget *= dotToTarget;
6002 float fof_spread_cooldown_factor = 1.0f - swp->primary_bank_fof_cooldown[swp->current_primary_bank];
6003
6004 //Combine factors
6005 float burstFireProb = ((0.6f * percentAmmoLeft) + (0.4f * distanceFactor)) * dotToTarget * aip->ai_primary_ammo_burst_mult * fof_spread_cooldown_factor;
6006
6007 //Possibly change values every half-second
6008 if (static_randf((Missiontime + static_rand(aip->shipnum)) >> 15) > burstFireProb)
6009 return 0;
6010 }
6011
6012 // Make sure not firing at a protected ship unless firing at a live subsystem.
6013 // Note: This happens every time the ship tries to fire, perhaps every frame.
6014 // Should be wrapped in a timestamp, same one that enables it to fire, but that is complicated
6015 // by multiple banks it can fire from.
6016 if (aip->target_objnum != -1) {
6017 object *tobjp = &Objects[aip->target_objnum];
6018 if (tobjp->flags[Object::Object_Flags::Protected]) {
6019 if (aip->targeted_subsys != NULL) {
6020 int type;
6021
6022 type = aip->targeted_subsys->system_info->type;
6023 if (ship_get_subsystem_strength(&Ships[tobjp->instance], type) == 0.0f) {
6024 aip->target_objnum = -1;
6025 return 0;
6026 }
6027 } else {
6028 aip->target_objnum = -1;
6029 return 0;
6030 }
6031 }
6032 }
6033
6034 // If enemy is protected, not firing a puncture weapon and enemy's hull is low, don't fire.
6035 if ((enemy_objp != NULL) && (enemy_objp->flags[Object::Object_Flags::Protected])) {
6036 // AL: 3-6-98: Check if current_primary_bank is valid
6037 if ((enemy_objp->hull_strength < 750.0f) &&
6038 ((aip->targeted_subsys == NULL) || (enemy_objp->hull_strength < aip->targeted_subsys->current_hits + 50.0f)) &&
6039 (swp->current_primary_bank >= 0) ) {
6040 if (!(Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].wi_flags[Weapon::Info_Flags::Puncture])) {
6041 swp->next_primary_fire_stamp[swp->current_primary_bank] = timestamp(1000);
6042 return 0;
6043 }
6044 }
6045 }
6046
6047 set_primary_weapon_linkage(objp);
6048
6049 // I think this will properly solve the problem
6050 // fire non-streaming weapons
6051 ship_fire_primary(objp, 0);
6052
6053 // fire streaming weapons
6054 shipp->flags.set(Ship::Ship_Flags::Trigger_down);
6055 ship_fire_primary(objp, 1);
6056 shipp->flags.remove(Ship::Ship_Flags::Trigger_down);
6057 return 1;//if it got down to here then it tryed to fire
6058 }
6059
6060 // --------------------------------------------------------------------------
6061 // Return number of nearby enemy fighters.
6062 // threshold is the distance within which a ship is considered near.
6063 //
6064 // input: enemy_team_mask => teams that are considered as an enemy
6065 // pos => world position to measure ship distances from
6066 // threshold => max distance from pos to be considered "near"
6067 //
6068 // exit: number of ships within threshold units of pos
num_nearby_fighters(int enemy_team_mask,vec3d * pos,float threshold)6069 int num_nearby_fighters(int enemy_team_mask, vec3d *pos, float threshold)
6070 {
6071 ship_obj *so;
6072 object *ship_objp;
6073 int count = 0;
6074
6075 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
6076
6077 ship_objp = &Objects[so->objnum];
6078
6079 if (iff_matches_mask(Ships[ship_objp->instance].team, enemy_team_mask)) {
6080 if (Ship_info[Ships[ship_objp->instance].ship_info_index].is_fighter_bomber()) {
6081 if (vm_vec_dist_quick(pos, &ship_objp->pos) < threshold)
6082 count++;
6083 }
6084 }
6085 }
6086
6087 return count;
6088 }
6089
6090 // --------------------------------------------------------------------------
6091 // Select secondary weapon to fire.
6092 // Currently, 1/16/98:
6093 // If 0 secondary weapons available, return -1
6094 // If 1 available, use it.
6095 // If 2 or more, if the current weapon is one of them, stick with it, otherwise choose a random one.
6096 // priority1 and priority2 are Weapon_info[] bitmasks such as WIF_HOMING_ASPECT. If any weapon has any bit in priority1
6097 // set, that weapon will be selected. If not, apply to priority2. If neither, return -1, meaning no weapon selected.
6098 // Note, priorityX have default values of -1, meaning if not set, they will match any weapon.
6099 // Return value:
6100 // bank index
6101 // Should do this:
6102 // Favor aspect seekers when attacking small ships faraway.
6103 // Favor rapid fire dumbfire when attacking a large ship.
6104 // Ignore heat seekers because we're not sure how they'll work.
ai_select_secondary_weapon(object * objp,ship_weapon * swp,flagset<Weapon::Info_Flags> * priority1=NULL,flagset<Weapon::Info_Flags> * priority2=NULL)6105 void ai_select_secondary_weapon(object *objp, ship_weapon *swp, flagset<Weapon::Info_Flags>* priority1 = NULL, flagset<Weapon::Info_Flags>* priority2 = NULL)
6106 {
6107 int num_weapon_types;
6108 int weapon_id_list[MAX_WEAPON_TYPES], weapon_bank_list[MAX_WEAPON_TYPES];
6109 int i;
6110 flagset<Weapon::Info_Flags> ignore_mask, ignore_mask_without_huge, prio1, prio2;
6111 int initial_bank;
6112 ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
6113
6114 initial_bank = swp->current_secondary_bank;
6115
6116 if (priority1 != NULL)
6117 prio1 = *priority1;
6118 if (priority2 != NULL)
6119 prio2 = *priority2;
6120
6121
6122 // set up ignore masks
6123 ignore_mask.reset();
6124
6125 // Ignore bombs unless one of the priorities asks for them to be selected.
6126 if (!(prio1[Weapon::Info_Flags::Huge] || prio2[Weapon::Info_Flags::Huge])) {
6127 ignore_mask.set(Weapon::Info_Flags::Huge);
6128 }
6129
6130 // Ignore capital+ unless one of the priorities asks for it.
6131 if (!(prio1[Weapon::Info_Flags::Capital_plus] || prio2[Weapon::Info_Flags::Capital_plus])) {
6132 ignore_mask.set(Weapon::Info_Flags::Capital_plus);
6133 }
6134
6135 // Ignore bomber+ unless one of the priorities asks for them to be selected
6136 if (!(prio1[Weapon::Info_Flags::Bomber_plus] || prio2[Weapon::Info_Flags::Bomber_plus])) {
6137 ignore_mask.set(Weapon::Info_Flags::Bomber_plus);
6138 }
6139
6140 #ifndef NDEBUG
6141 for (i=0; i<MAX_WEAPON_TYPES; i++) {
6142 weapon_id_list[i] = -1;
6143 weapon_bank_list[i] = -1;
6144 }
6145 #endif
6146
6147 // Stuff weapon_bank_list with bank index of available weapons.
6148 num_weapon_types = get_available_secondary_weapons(objp, weapon_id_list, weapon_bank_list);
6149
6150 // Ignore homing weapons if we didn't specify a flag - for priority 1
6151 if ((aip->ai_profile_flags[AI::Profile_Flags::Smart_secondary_weapon_selection]) && (prio1.none_set())) {
6152 ignore_mask.set(Weapon::Info_Flags::Homing_aspect).set(Weapon::Info_Flags::Homing_heat).set(Weapon::Info_Flags::Homing_javelin);
6153 }
6154
6155 ignore_mask_without_huge = ignore_mask;
6156 ignore_mask_without_huge.remove(Weapon::Info_Flags::Huge).remove(Weapon::Info_Flags::Capital_plus);
6157
6158 int priority2_index = -1;
6159
6160 for (i=0; i<num_weapon_types; i++) {
6161 auto wi_flags = Weapon_info[swp->secondary_bank_weapons[weapon_bank_list[i]]].wi_flags;
6162 auto ignore_mask_to_use = ((aip->ai_profile_flags[AI::Profile_Flags::Smart_secondary_weapon_selection]) && (wi_flags[Weapon::Info_Flags::Bomber_plus])) ? ignore_mask_without_huge : ignore_mask;
6163
6164 if (!(wi_flags & ignore_mask_to_use).any_set()) { // Maybe bombs are illegal.
6165 if ((wi_flags & prio1).any_set()) {
6166 swp->current_secondary_bank = weapon_bank_list[i]; // Found first priority, return it.
6167 break;
6168 } else if ((wi_flags & prio2).any_set())
6169 priority2_index = weapon_bank_list[i]; // Found second priority, but might still find first priority.
6170 }
6171 }
6172
6173 // Ignore homing weapons if we didn't specify a flag - for priority 2
6174 if ((aip->ai_profile_flags[AI::Profile_Flags::Smart_secondary_weapon_selection]) && (prio2.none_set())) {
6175 ignore_mask.set(Weapon::Info_Flags::Homing_aspect).set(Weapon::Info_Flags::Homing_heat).set(Weapon::Info_Flags::Homing_javelin);
6176 }
6177
6178 // If didn't find anything above, then pick any secondary weapon.
6179 if (i == num_weapon_types) {
6180 swp->current_secondary_bank = priority2_index; // Assume we won't find anything.
6181 if (priority2_index == -1) {
6182 for (i=0; i<num_weapon_types; i++) {
6183 auto wi_flags = Weapon_info[swp->secondary_bank_weapons[weapon_bank_list[i]]].wi_flags;
6184 auto ignore_mask_to_use = ((aip->ai_profile_flags[AI::Profile_Flags::Smart_secondary_weapon_selection]) && (wi_flags[Weapon::Info_Flags::Bomber_plus])) ? (ignore_mask - Weapon::Info_Flags::Huge) : ignore_mask;
6185
6186 if (!(wi_flags & ignore_mask_to_use).any_set()) { // Maybe bombs are illegal.
6187 if (swp->secondary_bank_ammo[weapon_bank_list[i]] > 0) {
6188 swp->current_secondary_bank = weapon_bank_list[i];
6189 break;
6190 }
6191 }
6192 }
6193 }
6194 }
6195
6196
6197 // If switched banks, force reacquisition of aspect lock.
6198 if (swp->current_secondary_bank != initial_bank) {
6199 aip->aspect_locked_time = 0.0f;
6200 aip->current_target_is_locked = 0;
6201 }
6202
6203 if (swp->current_secondary_bank >= 0 && swp->current_secondary_bank < MAX_SHIP_SECONDARY_BANKS)
6204 {
6205 weapon_info *wip=&Weapon_info[swp->secondary_bank_weapons[swp->current_secondary_bank]];
6206
6207 // phreak -- rapid dumbfire? let it rip!
6208 if ((aip->ai_profile_flags[AI::Profile_Flags::Allow_rapid_secondary_dumbfire]) && !(wip->is_homing()) && (wip->fire_wait < .5f))
6209 {
6210 aip->ai_flags.set(AI::AI_Flags::Unload_secondaries);
6211 }
6212 }
6213
6214 ship_secondary_changed(&Ships[objp->instance]); // AL: let multiplayer know if secondary bank has changed
6215 }
6216
6217 /**
6218 * @return number of objects homing on object *target_objp
6219 */
compute_num_homing_objects(object * target_objp)6220 int compute_num_homing_objects(object *target_objp)
6221 {
6222 object *objp;
6223 int count = 0;
6224
6225 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
6226 if (objp->type == OBJ_WEAPON) {
6227 if (Weapon_info[Weapons[objp->instance].weapon_info_index].is_homing()) {
6228 if (Weapons[objp->instance].homing_object == target_objp) {
6229 count++;
6230 }
6231 }
6232 }
6233 }
6234
6235 return count;
6236 }
6237
6238 // Object *firing_objp just fired weapon weapon_index (index in Weapon_info).
6239 // If it's a shockwave weapon, tell your team about it!
ai_maybe_announce_shockwave_weapon(object * firing_objp,int weapon_index)6240 void ai_maybe_announce_shockwave_weapon(object *firing_objp, int weapon_index)
6241 {
6242 if ((firing_objp->type == OBJ_SHIP) && (Weapon_info[weapon_index].shockwave.speed > 0.0f)) {
6243 ship_obj *so;
6244 int firing_ship_team;
6245
6246 firing_ship_team = Ships[firing_objp->instance].team;
6247
6248 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
6249 object *A = &Objects[so->objnum];
6250 Assert(A->type == OBJ_SHIP);
6251
6252 if (Ships[A->instance].team == firing_ship_team) {
6253 ai_info *aip = &Ai_info[Ships[A->instance].ai_index];
6254
6255 // AL 1-5-98: only avoid shockwave if not docked or repairing
6256 if ( !object_is_docked(A) && !(aip->ai_flags[AI::AI_Flags::Repairing, AI::AI_Flags::Being_repaired]) ) {
6257 aip->ai_flags.set(AI::AI_Flags::Avoid_shockwave_weapon);
6258 }
6259 }
6260 }
6261 }
6262 }
6263
6264 /**
6265 * Return total payload of all incoming missiles.
6266 */
compute_incoming_payload(object * target_objp)6267 float compute_incoming_payload(object *target_objp)
6268 {
6269 missile_obj *mo;
6270 float payload = 0.0f;
6271
6272 for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
6273 object *objp;
6274
6275 objp = &Objects[mo->objnum];
6276 Assert(objp->type == OBJ_WEAPON);
6277 if (Weapons[objp->instance].homing_object == target_objp) {
6278 payload += Weapon_info[Weapons[objp->instance].weapon_info_index].damage;
6279 }
6280 }
6281
6282 return payload;
6283 }
6284
6285 // --------------------------------------------------------------------------
6286 // Return true if OK for *aip to fire its current weapon at its current target.
6287 // Only reason this function returns false is:
6288 // weapon is a homer
6289 // targeted at player
6290 // OR: player has too many homers targeted at him
6291 // Missiontime in that dead zone in which can't fire at this player
6292 // OR: secondary los flag is set but no line of sight is availabe
6293 // Note: If player is attacking a ship, that ship is allowed to fire at player. Otherwise, we get in a situation in which
6294 // player is attacking a large ship, but that large ship is not defending itself with missiles.
check_ok_to_fire(int objnum,int target_objnum,weapon_info * wip,int secondary_bank,ship_subsys * turret)6295 int check_ok_to_fire(int objnum, int target_objnum, weapon_info *wip, int secondary_bank, ship_subsys* turret)
6296 {
6297 int num_homers = 0;
6298
6299 if (target_objnum >= 0) {
6300 object *tobjp = &Objects[target_objnum];
6301
6302 // AL 3-4-98: Ensure objp target is a ship first
6303 if ( tobjp->type == OBJ_SHIP ) {
6304 if (Ship_info[Ships[tobjp->instance].ship_info_index].is_small_ship()) {
6305 num_homers = compute_num_homing_objects(&Objects[target_objnum]);
6306 }
6307 }
6308
6309 if (The_mission.ai_profile->flags[AI::Profile_Flags::Require_exact_los] || wip->wi_flags[Weapon::Info_Flags::Require_exact_los]) {
6310 //Check if th AI has Line of Sight and is allowed to fire
6311 if (!check_los(objnum, target_objnum, 10.0f, -1, secondary_bank, turret)) {
6312 return 0;
6313 }
6314 }
6315
6316 // If player, maybe fire based on Skill_level and number of incoming weapons.
6317 // If non-player, maybe fire based on payload of incoming weapons.
6318 if (wip->is_homing()) {
6319 if ((target_objnum > -1) && (tobjp->flags[Object::Object_Flags::Player_ship])) {
6320 if (Ai_info[Ships[tobjp->instance].ai_index].target_objnum != objnum) {
6321 // Don't allow AI ships to fire at player for fixed periods of time based on skill level.
6322 // With 5 skill levels, at Very Easy, they fire in 1/7 of every 10 second interval.
6323 // At Easy, 2/7...at Expert, 5/7
6324 int t = ((Missiontime /(65536*10)) ^ target_objnum ^ 0x01) % (NUM_SKILL_LEVELS+2);
6325 if (t > Ai_info[Ships[tobjp->instance].ai_index].ai_chance_to_use_missiles_on_plr) {
6326 return 0;
6327 }
6328 }
6329 int swarmers = 0;
6330 if (wip->wi_flags[Weapon::Info_Flags::Swarm])
6331 swarmers = 2; // Note, always want to be able to fire swarmers if no currently incident homers.
6332 if (The_mission.ai_profile->max_allowed_player_homers[Game_skill_level] < num_homers + swarmers) {
6333 return 0;
6334 }
6335 } else if (num_homers > 3) {
6336 float incoming_payload;
6337
6338 incoming_payload = compute_incoming_payload(&Objects[target_objnum]);
6339
6340 if (incoming_payload > tobjp->hull_strength) {
6341 return 0;
6342 }
6343 }
6344 }
6345 }
6346 else
6347 {
6348 // We have no valid target object, we should not fire at it...
6349 return 0;
6350 }
6351
6352 return 1;
6353 }
6354
6355 // --------------------------------------------------------------------------
6356 // Returns true if *aip has a line of sight to its current target.
6357 // threshold defines the minimum radius for an object to be considered relevant for LoS
check_los(int objnum,int target_objnum,float threshold,int primary_bank,int secondary_bank,ship_subsys * turret)6358 bool check_los(int objnum, int target_objnum, float threshold, int primary_bank, int secondary_bank, ship_subsys* turret) {
6359
6360 //Do both checks over an XOR-Check for more detailed messages
6361 int sources = (primary_bank != -1 ? 1 : 0) + (secondary_bank != -1 ? 1 : 0) + (turret != nullptr ? 1 : 0);
6362 Assertion(sources >= 1, "Cannot check line of sight from a model with neither a primar bank, nor secondary bank nor a turret set as a source.");
6363 Assertion(sources <= 1, "Cannot check line of sight from a model with more than one source set.");
6364
6365 vec3d start;
6366
6367 object* firing_ship = &Objects[objnum];
6368
6369 if (turret != nullptr) {
6370 vec3d turret_fvec;
6371 //It doesn't like not having an fvec output vector, so give it a dummy vector
6372 ship_get_global_turret_gun_info(firing_ship, turret, &start, &turret_fvec, 1, nullptr);
6373 } else {
6374 bool is_primary = secondary_bank == -1;
6375 ship *shipp = &Ships[firing_ship->instance];
6376 ship_weapon *swp = &shipp->weapons;
6377 weapon_info *wip = &Weapon_info[is_primary ? swp->primary_bank_weapons[primary_bank] : swp->secondary_bank_weapons[secondary_bank]];
6378 polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num);
6379
6380 vec3d pnt = is_primary ? pm->gun_banks[primary_bank].pnt[swp->primary_next_slot[primary_bank]] : pm->missile_banks[secondary_bank].pnt[swp->secondary_next_slot[secondary_bank]];
6381 vec3d firing_point;
6382
6383 //Following Section taken from ship.cpp ship_fire_secondary()
6384 polymodel* weapon_model = nullptr;
6385 if (wip->external_model_num >= 0) {
6386 weapon_model = model_get(wip->external_model_num);
6387 }
6388
6389 if (weapon_model && weapon_model->n_guns) {
6390 int external_bank = is_primary ? primary_bank : secondary_bank + MAX_SHIP_PRIMARY_BANKS;
6391 if (wip->wi_flags[Weapon::Info_Flags::External_weapon_fp]) {
6392 if ((weapon_model->n_guns <= swp->external_model_fp_counter[external_bank]) || (swp->external_model_fp_counter[external_bank] < 0))
6393 swp->external_model_fp_counter[external_bank] = 0;
6394 vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[swp->external_model_fp_counter[external_bank]]);
6395 swp->external_model_fp_counter[external_bank]++;
6396 }
6397 else {
6398 // make it use the 0 index slot
6399 vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[0]);
6400 }
6401 }
6402 vm_vec_unrotate(&firing_point, &pnt, &firing_ship->orient);
6403 vm_vec_add(&start, &firing_point, &firing_ship->pos);
6404 }
6405
6406 vec3d& end = Objects[target_objnum].pos;
6407
6408 object* objp;
6409
6410 for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
6411 //Don't collision check against ourselves or our target
6412 if (OBJ_INDEX(objp) == objnum || OBJ_INDEX(objp) == target_objnum)
6413 continue;
6414
6415 int model_num = 0;
6416 int model_instance_num = 0;
6417
6418 //Only collision check against other pieces of Debris, Asteroids or Ships
6419 char type = objp->type;
6420 if (type == OBJ_DEBRIS) {
6421 model_num = Debris[objp->instance].model_num;
6422 model_instance_num = -1;
6423 }
6424 else if (type == OBJ_ASTEROID) {
6425 model_num = Asteroid_info[Asteroids[objp->instance].asteroid_type].model_num[Asteroids[objp->instance].asteroid_subtype];
6426 model_instance_num = Asteroids[objp->instance].model_instance_num;
6427 }
6428 else if (type == OBJ_SHIP) {
6429 model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
6430 model_instance_num = Ships[objp->instance].model_instance_num;
6431 }
6432 else
6433 continue;
6434
6435 //Early Out Model too small
6436
6437 float radius = objp->radius;
6438 if (radius < threshold)
6439 continue;
6440
6441 //Alternate "is in cylinder relevant for LoS" check
6442 /*vec3d a, b, c;
6443 vm_vec_sub(&a, &start, &objp->pos);
6444 vm_vec_sub(&b, &end, &start);
6445 vm_vec_cross(&c, &b, &a);
6446
6447 float distToTargetRecip = 1.0f / vm_vec_dist_squared(&start, &end);
6448 float distLoSSquared = vm_vec_mag_squared(&c) * distToTargetRecip;
6449 float t = -vm_vec_dot(&a, &b) * distToTargetRecip;
6450
6451 radius *= radius;
6452 float maxTdelta = sqrtf(radius * distToTargetRecip);
6453
6454 //Early out Model too far from LoS
6455 if (distLoSSquared > radius || -maxTdelta > t || maxTdelta + 1 < t)
6456 continue;
6457 */
6458
6459 //Asteroth's implementation
6460 // if objp is inside start or end then we have to check the model
6461 if (vm_vec_dist(&start, &objp->pos) > objp->radius && vm_vec_dist(&end, &objp->pos) > objp->radius) {
6462 // now check that objp is in between start and end
6463 vec3d start2end = end - start;
6464 vec3d end2start = start - end;
6465 vec3d start2objp = objp->pos - start;
6466 vec3d end2objp = objp->pos - end;
6467
6468 // if objp and end are in opposite directions from start, then early out
6469 if (vm_vec_dot(&start2end, &start2objp) < 0.0f)
6470 continue;
6471
6472 // if objp and start are in opposite directions from end, then early out
6473 if (vm_vec_dot(&end2start, &end2objp) < 0.0f)
6474 continue;
6475
6476 // finally check if objp is too close to the path
6477 if (vm_vec_mag(&start2objp) > (vm_vec_mag(&start2end) + objp->radius))
6478 continue; // adding objp->radius is somewhat of an overestimate but thats ok
6479 }
6480
6481 mc_info hull_check;
6482 mc_info_init(&hull_check);
6483
6484 hull_check.model_instance_num = model_instance_num;
6485 hull_check.model_num = model_num;
6486 hull_check.orient = &objp->orient;
6487 hull_check.pos = &objp->pos;
6488 hull_check.p0 = &start;
6489 hull_check.p1 = &end;
6490 hull_check.flags = MC_CHECK_MODEL;
6491
6492 if (model_collide(&hull_check)) {
6493 return false;
6494 }
6495 }
6496
6497 return true;
6498 }
6499
6500 // --------------------------------------------------------------------------
6501 // Fire a secondary weapon.
6502 // Maybe choose to fire a different one.
ai_fire_secondary_weapon(object * objp)6503 int ai_fire_secondary_weapon(object *objp)
6504 {
6505 ship_weapon *swp;
6506 ship *shipp;
6507 int current_bank;
6508 int rval = 0;
6509
6510 #ifndef NDEBUG
6511 if (!Ai_firing_enabled)
6512 return rval;
6513 #endif
6514
6515 Assert( objp != NULL );
6516 Assert(objp->type == OBJ_SHIP);
6517 shipp = &Ships[objp->instance];
6518 swp = &shipp->weapons;
6519
6520 Assert( shipp->ship_info_index >= 0 && shipp->ship_info_index < ship_info_size());
6521
6522 // Select secondary weapon.
6523 current_bank = swp->current_secondary_bank;
6524
6525 if (current_bank == -1) {
6526 return rval;
6527 }
6528
6529 Assert(current_bank < shipp->weapons.num_secondary_banks);
6530
6531 weapon_info *wip = &Weapon_info[shipp->weapons.secondary_bank_weapons[current_bank]];
6532
6533 if ((wip->is_locked_homing()) && (!Ai_info[shipp->ai_index].current_target_is_locked)) {
6534 swp->next_secondary_fire_stamp[current_bank] = timestamp(250);
6535 } else if ((wip->wi_flags[Weapon::Info_Flags::Bomb]) || (vm_vec_dist_quick(&objp->pos, &En_objp->pos) > 50.0f)) {
6536 // This might look dumb, firing a bomb even if closer than 50 meters, but the reason is, if you're carrying
6537 // bombs, delivering them is probably more important than surviving.
6538 ai_info *aip;
6539
6540 aip = &Ai_info[shipp->ai_index];
6541
6542 // Note, maybe don't fire if firing at player and any homers yet fired.
6543 // Decreasing chance to fire the more homers are incoming on player.
6544 if (check_ok_to_fire(OBJ_INDEX(objp), aip->target_objnum, wip, current_bank, nullptr)) {
6545
6546 if (wip->multi_lock && !ai_do_multilock(aip, wip))
6547 return rval;
6548
6549 if (ship_fire_secondary(objp)) {
6550 rval = 1;
6551 swp->next_secondary_fire_stamp[current_bank] = timestamp(500);
6552
6553 if (wip->multi_lock && wip->launch_reset_locks) {
6554 aip->aspect_locked_time = 0.0f;
6555 aip->current_target_is_locked = 0;
6556 }
6557 }
6558
6559 } else {
6560 swp->next_secondary_fire_stamp[current_bank] = timestamp(500);
6561 }
6562 }
6563
6564 return rval;
6565 }
6566
6567 /**
6568 * Return true if it looks like obj1, if continuing to move along current vector, will collide with obj2.
6569 */
might_collide_with_ship(object * obj1,object * obj2,float dot_to_enemy,float dist_to_enemy,float duration)6570 int might_collide_with_ship(object *obj1, object *obj2, float dot_to_enemy, float dist_to_enemy, float duration)
6571 {
6572 if (obj1->phys_info.speed * duration + 2*(obj1->radius + obj2->radius) > dist_to_enemy)
6573 if (dot_to_enemy > 0.8f - 2*(obj1->radius + obj2->radius)/dist_to_enemy)
6574 return objects_will_collide(obj1, obj2, duration, 2.0f);
6575
6576 return 0;
6577 }
6578
6579 /**
6580 * Return true if ship *objp firing a laser believes it will hit a teammate.
6581 */
might_hit_teammate(object * firing_objp)6582 int might_hit_teammate(object *firing_objp)
6583 {
6584 int team;
6585 object *objp;
6586 ship_obj *so;
6587
6588 team = Ships[firing_objp->instance].team;
6589
6590 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
6591 objp = &Objects[so->objnum];
6592 if (Ships[objp->instance].team == team) {
6593 float dist, dot;
6594 vec3d vec_to_objp;
6595
6596 vm_vec_sub(&vec_to_objp, &firing_objp->pos, &objp->pos);
6597 dist = vm_vec_mag_quick(&vec_to_objp);
6598 dot = vm_vec_dot(&firing_objp->orient.vec.fvec, &vec_to_objp)/dist;
6599 if (might_collide_with_ship(firing_objp, objp, dot, dist, 2.0f))
6600 return 1;
6601 }
6602 }
6603
6604 return 0;
6605 }
6606
render_all_ship_bay_paths(object * objp)6607 void render_all_ship_bay_paths(object *objp)
6608 {
6609 int i,j,clr;
6610 polymodel *pm;
6611 model_path *mp;
6612
6613 pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
6614 vec3d global_path_point;
6615 vertex v, prev_vertex;
6616
6617 if ( pm->ship_bay == NULL )
6618 return;
6619
6620 memset(&v, 0, sizeof(v));
6621
6622 for ( i = 0; i < pm->ship_bay->num_paths; i++ ) {
6623 mp = &pm->paths[pm->ship_bay->path_indexes[i]];
6624
6625 for ( j = 0; j < mp->nverts; j++ ) {
6626 vm_vec_unrotate(&global_path_point, &mp->verts[j].pos, &objp->orient);
6627 vm_vec_add2(&global_path_point, &objp->pos);
6628 g3_rotate_vertex(&v, &global_path_point);
6629 clr = 255 - j*50;
6630 if ( clr < 50 )
6631 clr = 100;
6632 gr_set_color(0, clr, 0);
6633
6634 if ( j == mp->nverts-1 ) {
6635 gr_set_color(255, 0, 0);
6636 }
6637
6638 g3_draw_sphere( &v, 1.5f);
6639
6640 if ( j > 0 )
6641 g3_draw_line(&v, &prev_vertex);
6642
6643 prev_vertex = v;
6644
6645 }
6646 }
6647 }
6648
6649 /**
6650 * Debug function to show all path points associated with an object
6651 */
render_all_subsys_paths(object * objp)6652 void render_all_subsys_paths(object *objp)
6653 {
6654 int i,j,clr;
6655 polymodel *pm;
6656 model_path *mp;
6657
6658 pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
6659 vec3d global_path_point;
6660 vertex v, prev_vertex;
6661
6662 if ( pm->ship_bay == NULL )
6663 return;
6664
6665 memset(&v, 0, sizeof(v));
6666
6667 for ( i = 0; i < pm->n_paths; i++ ) {
6668 mp = &pm->paths[i];
6669 for ( j = 0; j < mp->nverts; j++ ) {
6670 vm_vec_unrotate(&global_path_point, &mp->verts[j].pos, &objp->orient);
6671 vm_vec_add2(&global_path_point, &objp->pos);
6672 g3_rotate_vertex(&v, &global_path_point);
6673 clr = 255 - j*50;
6674 if ( clr < 50 )
6675 clr = 100;
6676 gr_set_color(0, clr, 0);
6677
6678 if ( j == mp->nverts-1 ) {
6679 gr_set_color(255, 0, 0);
6680 }
6681
6682 g3_draw_sphere( &v, 1.5f);
6683
6684 if ( j > 0 )
6685 g3_draw_line(&v, &prev_vertex);
6686
6687 prev_vertex = v;
6688 }
6689 }
6690 }
6691
render_path_points(object * objp)6692 void render_path_points(object *objp)
6693 {
6694 ship *shipp = &Ships[objp->instance];
6695 ai_info *aip = &Ai_info[shipp->ai_index];
6696 object *dobjp;
6697
6698 render_all_subsys_paths(objp);
6699 render_all_ship_bay_paths(objp);
6700
6701 if (aip->goal_objnum < 0)
6702 return;
6703
6704 dobjp = &Objects[aip->goal_objnum];
6705 vec3d dock_point, global_dock_point;
6706 vertex v;
6707
6708 auto pmi = model_get_instance(Ships[dobjp->instance].model_instance_num);
6709 auto pm = model_get(pmi->model_num);
6710
6711 if (pm->n_docks) {
6712 dock_point = pm->docking_bays[0].pnt[0];
6713 model_instance_find_world_point(&global_dock_point, &dock_point, pm, pmi, 0, &dobjp->orient, &dobjp->pos );
6714 g3_rotate_vertex(&v, &global_dock_point);
6715 gr_set_color(255, 255, 255);
6716 g3_draw_sphere( &v, 1.5f);
6717 }
6718
6719 if (aip->path_start != -1) {
6720 vertex prev_vertex;
6721 pnode *pp = &Path_points[aip->path_start];
6722 int num_points = aip->path_length;
6723 int i;
6724
6725 for (i=0; i<num_points; i++) {
6726 vertex v0;
6727
6728 memset(&v0, 0, sizeof(v0));
6729
6730 g3_rotate_vertex( &v0, &pp->pos );
6731
6732 gr_set_color(0, 128, 96);
6733 if (i != 0)
6734 g3_draw_line(&v0, &prev_vertex);
6735
6736 if (pp-Path_points == aip->path_cur)
6737 gr_set_color(255,255,0);
6738
6739 g3_draw_sphere( &v0, 4.5f);
6740
6741 prev_vertex = v0;
6742
6743 pp++;
6744 }
6745 }
6746 }
6747
6748 /**
6749 * Return the distance that the current AI weapon will travel
6750 */
ai_get_weapon_dist(ship_weapon * swp)6751 float ai_get_weapon_dist(ship_weapon *swp)
6752 {
6753 int bank_num, weapon_num;
6754
6755 bank_num = swp->current_primary_bank;
6756 weapon_num = swp->primary_bank_weapons[bank_num];
6757
6758 // If weapon_num is illegal, return a reasonable value. A valid weapon
6759 // will get selected when this ship tries to fire.
6760 if (weapon_num == -1) {
6761 return 1000.0f;
6762 }
6763
6764 Assertion(weapon_num >= 0, "A ship's weapon has a nonsense index of %d, but the ship's name is not accessible from this function. Please report!", weapon_num);
6765
6766 return MIN((Weapon_info[weapon_num].max_speed * Weapon_info[weapon_num].lifetime), Weapon_info[weapon_num].weapon_range);
6767 }
6768
ai_get_weapon_speed(ship_weapon * swp)6769 float ai_get_weapon_speed(ship_weapon *swp)
6770 {
6771 int bank_num, weapon_num;
6772
6773 bank_num = swp->current_primary_bank;
6774 if (bank_num < 0)
6775 return 100.0f;
6776
6777 weapon_num = swp->primary_bank_weapons[bank_num];
6778
6779 if (weapon_num == -1) {
6780 return 100.0f;
6781 }
6782
6783 Assertion(weapon_num >= 0, "A ship's weapon has a nonsense index of %d, but the ship's name is not accessible from this function. Please report!", weapon_num);
6784
6785 return Weapon_info[weapon_num].max_speed;
6786 }
6787
ai_get_weapon(ship_weapon * swp)6788 weapon_info* ai_get_weapon(ship_weapon *swp)
6789 {
6790 int bank_num, weapon_num;
6791
6792 bank_num = swp->current_primary_bank;
6793 if (bank_num < 0)
6794 return nullptr;
6795
6796 weapon_num = swp->primary_bank_weapons[bank_num];
6797
6798 if (weapon_num == -1) {
6799 return nullptr;
6800 }
6801
6802 Assertion(weapon_num >= 0, "A ship's weapon has a nonsense index of %d, but the ship's name is not accessible from this function. Please report!", weapon_num);
6803
6804 return &Weapon_info[weapon_num];
6805 }
6806
6807 // Compute the predicted position of a ship to be fired upon from a turret.
6808 // This is based on position of firing gun, enemy object, weapon speed and skill level constraints.
6809 // Return value in *predicted_enemy_pos.
6810 // Also, stuff globals G_predicted_pos, G_collision_time and G_fire_pos.
6811 // *pobjp object firing the weapon
6812 // *eobjp object being fired upon
set_predicted_enemy_pos_turret(vec3d * predicted_enemy_pos,vec3d * gun_pos,object * pobjp,vec3d * enemy_pos,vec3d * enemy_vel,float weapon_speed,float time_enemy_in_range)6813 void set_predicted_enemy_pos_turret(vec3d *predicted_enemy_pos, vec3d *gun_pos, object *pobjp, vec3d *enemy_pos, vec3d *enemy_vel, float weapon_speed, float time_enemy_in_range)
6814 {
6815 ship *shipp = &Ships[pobjp->instance];
6816 float range_time;
6817
6818 if (weapon_speed < 1.0f)
6819 weapon_speed = 1.0f;
6820
6821 range_time = 2.0f;
6822
6823 // Make it take longer for enemies to get player's allies in range based on skill level.
6824 if (iff_x_attacks_y(Ships[pobjp->instance].team, Player_ship->team))
6825 range_time += Ai_info[shipp->ai_index].ai_in_range_time;
6826
6827 if (time_enemy_in_range < range_time) {
6828 float dist;
6829
6830 dist = vm_vec_dist_quick(&pobjp->pos, enemy_pos);
6831 vm_vec_scale_add(predicted_enemy_pos, enemy_pos, enemy_vel, time_enemy_in_range * dist/weapon_speed);
6832 } else {
6833 float collision_time, scale;
6834 vec3d rand_vec;
6835 ai_info *aip = &Ai_info[shipp->ai_index];
6836
6837 collision_time = compute_collision_time(enemy_pos, enemy_vel, gun_pos, weapon_speed);
6838
6839 if (collision_time == 0.0f){
6840 collision_time = 100.0f;
6841 }
6842
6843 vm_vec_scale_add(predicted_enemy_pos, enemy_pos, enemy_vel, collision_time);
6844 if (time_enemy_in_range > 2*range_time){
6845 scale = (1.0f - aip->ai_accuracy) * 4.0f;
6846 } else {
6847 scale = (1.0f - aip->ai_accuracy) * 4.0f * (1.0f + 4.0f * (1.0f - time_enemy_in_range/(2*range_time)));
6848 }
6849
6850 static_randvec(((OBJ_INDEX(pobjp)) ^ (Missiontime >> 16)) & 7, &rand_vec);
6851
6852 vm_vec_scale_add2(predicted_enemy_pos, &rand_vec, scale);
6853 G_collision_time = collision_time;
6854 G_fire_pos = *gun_pos;
6855 }
6856
6857 G_predicted_pos = *predicted_enemy_pos;
6858 }
6859
6860 // Compute the predicted position of a ship to be fired upon.
6861 // This is based on current position of firing object, enemy object, relative position of gun on firing object,
6862 // weapon speed and skill level constraints.
6863 // Return value in *predicted_enemy_pos.
6864 // Also, stuff globals G_predicted_pos, G_collision_time and G_fire_pos.
6865 //SUSHI: Modified to take in a position and accel value instead of reading it directly from the enemy object
set_predicted_enemy_pos(vec3d * predicted_enemy_pos,object * pobjp,vec3d * enemy_pos,vec3d * enemy_vel,ai_info * aip)6866 void set_predicted_enemy_pos(vec3d *predicted_enemy_pos, object *pobjp, vec3d *enemy_pos, vec3d *enemy_vel, ai_info *aip)
6867 {
6868 float weapon_speed, range_time;
6869 ship *shipp = &Ships[pobjp->instance];
6870 weapon_info *wip;
6871 vec3d target_moving_direction;
6872
6873 Assert( enemy_pos != NULL );
6874 Assert( enemy_vel != NULL );
6875
6876 wip = ai_get_weapon(&shipp->weapons);
6877 target_moving_direction = *enemy_vel;
6878
6879 if (wip != NULL && The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity])
6880 vm_vec_scale_sub2(&target_moving_direction, &pobjp->phys_info.vel, wip->vel_inherit_amount);
6881
6882 if (wip != NULL)
6883 weapon_speed = wip->max_speed;
6884 else
6885 weapon_speed = 100.0f;
6886
6887 weapon_speed = MAX(weapon_speed, 1.0f); // set not less than 1
6888
6889 range_time = 2.0f;
6890
6891 // Make it take longer for enemies to get player's allies in range based on skill level.
6892 // but don't bias team v. team missions
6893 if ( !(MULTI_TEAM) )
6894 {
6895 if (iff_x_attacks_y(shipp->team, Player_ship->team))
6896 range_time += aip->ai_in_range_time;
6897 }
6898
6899 if (aip->time_enemy_in_range < range_time) {
6900 float dist;
6901
6902 dist = vm_vec_dist_quick(&pobjp->pos, enemy_pos);
6903 vm_vec_scale_add(predicted_enemy_pos, enemy_pos, &target_moving_direction, aip->time_enemy_in_range * dist/weapon_speed);
6904 } else {
6905 float collision_time;
6906 vec3d gun_pos, pnt;
6907 polymodel *pm = model_get(Ship_info[shipp->ship_info_index].model_num);
6908
6909 // Compute position of gun in absolute space and use that as fire position
6910 // ...unless we want to just use the ship center
6911 if(pm->gun_banks != NULL && !(The_mission.ai_profile->flags[AI::Profile_Flags::Ai_aims_from_ship_center])){
6912 pnt = pm->gun_banks[0].pnt[0];
6913 } else {
6914 //Use the convergence offset, if there is one
6915 vm_vec_copy_scale(&pnt, &Ship_info[shipp->ship_info_index].convergence_offset, 1.0f);
6916 }
6917 vm_vec_unrotate(&gun_pos, &pnt, &pobjp->orient);
6918 vm_vec_add2(&gun_pos, &pobjp->pos);
6919
6920 collision_time = compute_collision_time(enemy_pos, &target_moving_direction, &gun_pos, weapon_speed);
6921
6922 if (collision_time == 0.0f) {
6923 collision_time = 100.0f;
6924 }
6925
6926 vm_vec_scale_add(predicted_enemy_pos, enemy_pos, &target_moving_direction, collision_time);
6927
6928 // set globals
6929 G_collision_time = collision_time;
6930 G_fire_pos = gun_pos;
6931 }
6932
6933 // Now add error terms (1) regular aim (2) EMP (3) stealth
6934 float scale = 0.0f;
6935 vec3d rand_vec;
6936
6937 // regular skill level error in aim
6938 if (aip->time_enemy_in_range > 2*range_time) {
6939 scale = (1.0f - aip->ai_accuracy) * 4.0f;
6940 } else {
6941 scale = (1.0f - aip->ai_accuracy) * 4.0f * (1.0f + 4.0f * (1.0f - aip->time_enemy_in_range/(2*range_time)));
6942 }
6943
6944 // if this ship is under the effect of an EMP blast, throw his aim off a bit
6945 if (shipp->emp_intensity > 0.0f) {
6946 // never go lower than 1/2 of the EMP effect max, otherwise things aren't noticeable
6947 scale += (MAX_EMP_INACCURACY * (shipp->emp_intensity < 0.5f ? 0.5f : shipp->emp_intensity));
6948 mprintf(("AI miss scale factor (EMP) %f\n",scale));
6949 }
6950
6951 // if stealthy ship, throw his aim off, more when farther away and when dot is small
6952 if ( aip->ai_flags[AI::AI_Flags::Stealth_pursuit] ) {
6953 float dist = vm_vec_dist_quick(&pobjp->pos, enemy_pos);
6954 vec3d temp;
6955 vm_vec_sub(&temp, enemy_pos, &pobjp->pos);
6956 vm_vec_normalize_quick(&temp);
6957 float dot = vm_vec_dot(&temp, &pobjp->orient.vec.fvec);
6958 float st_err = 3.0f * (1.4f - dot) * (1.0f + dist / (get_skill_stealth_dist_scaler() * STEALTH_MAX_VIEW_DIST)) * (1 - aip->ai_accuracy);
6959 scale += st_err;
6960 }
6961
6962 // get a random vector that changes slowly over time (1x / sec)
6963 static_randvec(((OBJ_INDEX(pobjp)) ^ (Missiontime >> 16)) & 7, &rand_vec);
6964
6965 vm_vec_scale_add2(predicted_enemy_pos, &rand_vec, scale);
6966
6967 // set global
6968 G_predicted_pos = *predicted_enemy_pos;
6969 }
6970
6971 /**
6972 * Handler of submode for Chase. Go into a continuous turn for awhile.
6973 */
ai_chase_ct()6974 void ai_chase_ct()
6975 {
6976 vec3d tvec;
6977 ai_info *aip;
6978
6979 Assert(Ships[Pl_objp->instance].ai_index >= 0);
6980 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
6981
6982 // Make a continuous turn towards any combination of possibly negated
6983 // up and right vectors.
6984 tvec = Pl_objp->pos;
6985
6986 if (aip->submode_parm0 & 0x01)
6987 vm_vec_add2(&tvec, &Pl_objp->orient.vec.rvec);
6988 if (aip->submode_parm0 & 0x02)
6989 vm_vec_sub2(&tvec, &Pl_objp->orient.vec.rvec);
6990 if (aip->submode_parm0 & 0x04)
6991 vm_vec_add2(&tvec, &Pl_objp->orient.vec.uvec);
6992 if (aip->submode_parm0 & 0x08)
6993 vm_vec_sub2(&tvec, &Pl_objp->orient.vec.uvec);
6994
6995 // Detect degenerate cases that cause tvec to be same as player pos.
6996 if (vm_vec_dist_quick(&tvec, &Pl_objp->pos) < 0.1f) {
6997 aip->submode_parm0 &= 0x05;
6998 if (aip->submode_parm0 == 0)
6999 aip->submode_parm0 = 1;
7000 vm_vec_add2(&tvec, &Pl_objp->orient.vec.rvec);
7001 }
7002
7003 ai_turn_towards_vector(&tvec, Pl_objp, nullptr, nullptr, 0.0f, 0);
7004 accelerate_ship(aip, 1.0f);
7005 }
7006
7007 /**
7008 * ATTACK submode handler for chase mode.
7009 */
ai_chase_eb(ai_info * aip,vec3d * predicted_enemy_pos)7010 static void ai_chase_eb(ai_info *aip, vec3d *predicted_enemy_pos)
7011 {
7012 vec3d _pep;
7013 float dot_to_enemy, dot_from_enemy;
7014
7015 compute_dots(Pl_objp, En_objp, &dot_to_enemy, &dot_from_enemy);
7016
7017 // If we're trying to slow down to get behind, then point to turn towards is different.
7018 _pep = *predicted_enemy_pos;
7019 if ((dot_to_enemy > dot_from_enemy + 0.1f) || (dot_to_enemy > 0.9f))
7020 vm_vec_scale_add(&_pep, &Pl_objp->pos, &En_objp->orient.vec.fvec, 100.0f);
7021
7022 ai_turn_towards_vector(&_pep, Pl_objp, nullptr, nullptr, 0.0f, 0);
7023
7024 accelerate_ship(aip, 0.0f);
7025 }
7026
7027 // Return time until weapon_objp might hit ship_objp.
7028 // Assumes ship_objp is not moving.
7029 // Returns negative time if not going to hit.
7030 // This is a very approximate function, but is pretty fast.
ai_endangered_time(object * ship_objp,object * weapon_objp)7031 float ai_endangered_time(object *ship_objp, object *weapon_objp)
7032 {
7033 float to_dot, from_dot, dist;
7034
7035 dist = compute_dots(ship_objp, weapon_objp, &to_dot, &from_dot);
7036
7037 // Note, this is bogus. It assumes only the weapon is moving.
7038 // Only proceed if weapon sort of pointing at object and object pointing towards or away from weapon
7039 // (Ie, if object moving at right angle to weapon, just continue for now...)
7040 if (weapon_objp->phys_info.speed < 1.0f)
7041 return dist + 1.0f;
7042 else if ((from_dot > 0.1f) && (dist/(from_dot*from_dot) < 48*ship_objp->radius)) //: don't require them to see it, they have instruments!: && (fl_abs(to_dot) > 0.5f))
7043 return dist / weapon_objp->phys_info.speed;
7044 else
7045 return -1.0f;
7046 }
7047
7048 // Return time until danger weapon could hit this ai object.
7049 // Return negative time if not endangered.
ai_endangered_by_weapon(ai_info * aip)7050 float ai_endangered_by_weapon(ai_info *aip)
7051 {
7052 object *weapon_objp;
7053
7054 if (aip->danger_weapon_objnum == -1) {
7055 return -1.0f;
7056 }
7057
7058 Assertion(aip->danger_weapon_objnum >= 0, "%s has a nonsense danger_weapon_objnum of %d. Please report!", Ships[aip->shipnum].ship_name, aip->danger_weapon_objnum);\
7059
7060 weapon_objp = &Objects[aip->danger_weapon_objnum];
7061
7062 if (weapon_objp->signature != aip->danger_weapon_signature) {
7063 aip->danger_weapon_objnum = -1;
7064 return -1.0f;
7065 }
7066
7067 return ai_endangered_time(&Objects[Ships[aip->shipnum].objnum], weapon_objp);
7068 }
7069
7070 // Return true if this ship is near full strength.
7071 // Goober5000: simplified and accounted for shields being 0
ai_near_full_strength(object * objp)7072 int ai_near_full_strength(object *objp)
7073 {
7074 return (get_hull_pct(objp) > 0.9f) || (get_shield_pct(objp) > 0.8f);
7075 }
7076
do_random_sidethrust(ai_info * aip,ship_info * sip)7077 void do_random_sidethrust(ai_info *aip, ship_info *sip)
7078 {
7079 //Sidethrust vector is initially based on the velocity vector representing the ship's current sideways motion.
7080 vec2d side_vec;
7081 //Length to hold circle strafe is random (1-3) + slide accel time, changes every 4 seconds
7082 int strafeHoldDirAmount = (int)(sip->slide_accel) + static_rand_range((Missiontime + static_rand(aip->shipnum)) >> 18, 1, 3);
7083 //Get a random float using some of the more significant chunks of the missiontime as a seed (>>16 means it changes every second)
7084 //This means that we get the same random values for a little bit.
7085 //Using static_rand(shipnum) as a crude hash function to make sure that the seed is different for each ship and direction
7086 //The *2 ensures that y and x stay separate.
7087 if (strafeHoldDirAmount > 0) { //This may look unnecessary, but we're apparently still getting div by zero errors on Macs here.
7088 side_vec.x = static_randf_range((((Missiontime + static_rand(aip->shipnum)) >> 16) / strafeHoldDirAmount) , -1.0f, 1.0f);
7089 side_vec.y = static_randf_range((((Missiontime + static_rand(aip->shipnum)) >> 16) / strafeHoldDirAmount) * 2, -1.0f, 1.0f);
7090 } else {
7091 Warning(LOCATION, "Division by zero in do_random_sidethrust averted. Please tell a coder.\n");
7092 side_vec.x = 1.0f;
7093 side_vec.y = 1.0f;
7094 }
7095 //Scale it up so that the longest dimension is length 1.0. This ensures we are always getting as much use out of sidethrust as possible.
7096 vm_vec_boxscale(&side_vec, 1.0f);
7097
7098 AI_ci.sideways = side_vec.x;
7099 AI_ci.vertical = side_vec.y;
7100 }
7101
7102 const float AI_DEFAULT_ATTACK_APPROACH_DIST = 200.0f;
7103
7104 /**
7105 * Set acceleration while in attack mode.
7106 */
attack_set_accel(ai_info * aip,ship_info * sip,float dist_to_enemy,float dot_to_enemy,float dot_from_enemy)7107 void attack_set_accel(ai_info *aip, ship_info *sip, float dist_to_enemy, float dot_to_enemy, float dot_from_enemy)
7108 {
7109 float speed_ratio;
7110
7111 if (En_objp->phys_info.speed > 1.0f)
7112 speed_ratio = Pl_objp->phys_info.speed/En_objp->phys_info.speed;
7113 else
7114 speed_ratio = 5.0f;
7115
7116 // Sometimes, told to attack slowly. Allows to get in more hits.
7117 if (aip->ai_flags[AI::AI_Flags::Attack_slowly]) {
7118 if ((dist_to_enemy > 200.0f) && (dist_to_enemy < 800.0f)) {
7119 if ((dot_from_enemy < 0.9f) || ai_near_full_strength(Pl_objp)) {
7120 accelerate_ship(aip, MAX(1.0f - (dist_to_enemy-200.0f)/600.0f, 0.1f));
7121 return;
7122 }
7123 }
7124 else
7125 aip->ai_flags.remove(AI::AI_Flags::Attack_slowly);
7126 }
7127
7128 //Glide attack: we turn on glide to maintain current vector while aiming at the enemy
7129 //The aiming part should already be taken care of.
7130 if (aip->submode == AIS_CHASE_GLIDEATTACK) {
7131 Pl_objp->phys_info.flags |= PF_GLIDING;
7132 accelerate_ship(aip, 0.0f);
7133 return;
7134 }
7135
7136 //Circle Strafe: We try to maintain a constant distance from the target while using sidethrust to move in a circle
7137 //around the target
7138 if (aip->submode == AIS_CHASE_CIRCLESTRAFE) {
7139 //If glide is available, use it (smooths things out a bit)
7140 if (sip->can_glide == true)
7141 Pl_objp->phys_info.flags |= PF_GLIDING;
7142
7143 //Try to maintain a distance between 50% and 75% of maximum circle strafe distance
7144 if (dist_to_enemy <= CIRCLE_STRAFE_MAX_DIST * .5)
7145 accelerate_ship(aip, -1.0f);
7146 else if (dist_to_enemy >= CIRCLE_STRAFE_MAX_DIST * 0.75)
7147 accelerate_ship(aip, 1.0f);
7148 else
7149 accelerate_ship(aip, 0.0f);
7150
7151 do_random_sidethrust(aip, sip);
7152 return;
7153 }
7154
7155 // assume the original retail value of a 200m range
7156 float optimal_range = AI_DEFAULT_ATTACK_APPROACH_DIST;
7157 ship_weapon* weapons = &Ships[Pl_objp->instance].weapons;
7158 weapon_info* wip = nullptr;
7159
7160 // see if we can get a better one
7161 if (weapons->num_primary_banks >= 1 && weapons->current_primary_bank >= 0) {
7162 wip = &Weapon_info[weapons->primary_bank_weapons[weapons->current_primary_bank]];
7163 } else if (weapons->num_secondary_banks >= 1 && weapons->current_secondary_bank >= 0) {
7164 wip = &Weapon_info[weapons->secondary_bank_weapons[weapons->current_secondary_bank]];
7165 }
7166
7167 if (wip != nullptr && wip->optimum_range > 0)
7168 optimal_range = wip->optimum_range;
7169
7170 if (dist_to_enemy > optimal_range + vm_vec_mag_quick(&En_objp->phys_info.vel) * dot_from_enemy + Pl_objp->phys_info.speed * speed_ratio) {
7171 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
7172 if (dist_to_enemy > optimal_range + 600.0f) {
7173 if (!( Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
7174 float percent_left;
7175 ship *shipp;
7176 ship_info *sip_local;
7177
7178 shipp = &Ships[Pl_objp->instance];
7179 sip_local = &Ship_info[shipp->ship_info_index];
7180
7181 if (sip_local->afterburner_fuel_capacity > 0.0f) {
7182 percent_left = 100.0f * shipp->afterburner_fuel / sip_local->afterburner_fuel_capacity;
7183 if (percent_left > 30.0f + ((OBJ_INDEX(Pl_objp)) & 0x0f)) {
7184 afterburners_start(Pl_objp);
7185 if (aip->ai_profile_flags[AI::Profile_Flags::Smart_afterburner_management]) {
7186 float max_ab_vel;
7187 float time_to_exhaust_25pct_fuel;
7188 float time_to_fly_75pct_of_distance;
7189 float ab_time;
7190
7191 // Max afterburner speed - make sure we don't devide by 0 later
7192 max_ab_vel = sip_local->afterburner_max_vel.xyz.z > 0.0f ? sip_local->afterburner_max_vel.xyz.z : sip_local->max_vel.xyz.z;
7193 max_ab_vel = max_ab_vel > 0.0f ? max_ab_vel : 0.0001f;
7194
7195 // Time to exhaust 25% of the remaining fuel
7196 time_to_exhaust_25pct_fuel = shipp->afterburner_fuel * 0.25f / sip_local->afterburner_burn_rate;
7197
7198 // Time to fly 75% of the distance to the target
7199 time_to_fly_75pct_of_distance = dist_to_enemy * 0.75f / max_ab_vel;
7200
7201 // Get minimum
7202 ab_time = MIN(time_to_exhaust_25pct_fuel, time_to_fly_75pct_of_distance);
7203
7204 aip->afterburner_stop_time = (fix) (Missiontime + F1_0 * ab_time);
7205 } else {
7206 aip->afterburner_stop_time = Missiontime + F1_0 + static_rand(OBJ_INDEX(Pl_objp))/4;
7207 }
7208 }
7209 }
7210 }
7211 }
7212 }
7213
7214 accelerate_ship(aip, 1.0f);
7215 } else if ((Missiontime - aip->last_hit_time > F1_0*7)
7216 && (En_objp->phys_info.speed < 10.0f)
7217 && (dist_to_enemy > 25.0f)
7218 && (dot_to_enemy > 0.8f)
7219 && (dot_from_enemy < 0.8f)) {
7220 accelerate_ship(aip, 0.0f); // No one attacking us, so don't need to move.
7221 } else if ((dot_from_enemy < 0.25f) && (dot_to_enemy > 0.5f)) {
7222 set_accel_for_target_speed(Pl_objp, En_objp->phys_info.speed);
7223 } else if (Pl_objp->phys_info.speed < 15.0f) {
7224 accelerate_ship(aip, 1.0f);
7225 } else if (Pl_objp->phys_info.speed > En_objp->phys_info.speed - 1.0f) {
7226 if (dot_from_enemy > 0.75f)
7227 accelerate_ship(aip, 1.0f);
7228 else
7229 set_accel_for_target_speed(Pl_objp, En_objp->phys_info.speed*0.75f + 3.0f);
7230 } else {
7231 change_acceleration(aip, 0.5f);
7232 }
7233 }
7234
7235 // Pl_objp (aip) tries to get behind En_objp.
7236 // New on 2/21/98: If this ship can move backwards and slide, maybe do that to get behind.
get_behind_ship(ai_info * aip)7237 static void get_behind_ship(ai_info *aip)
7238 {
7239 vec3d new_pos;
7240 float dot;
7241 vec3d vec_from_enemy;
7242
7243 vm_vec_normalized_dir(&vec_from_enemy, &Pl_objp->pos, &En_objp->pos);
7244
7245 vm_vec_scale_add(&new_pos, &En_objp->pos, &En_objp->orient.vec.fvec, -100.0f); // Pick point 100 units behind.
7246 ai_turn_towards_vector(&new_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
7247
7248 dot = vm_vec_dot(&vec_from_enemy, &En_objp->orient.vec.fvec);
7249
7250 if (dot > 0.25f) {
7251 accelerate_ship(aip, 1.0f);
7252 } else {
7253 accelerate_ship(aip, (dot + 1.0f)/2.0f);
7254 }
7255 }
7256
avoid_player(object * objp,vec3d * goal_pos)7257 int avoid_player(object *objp, vec3d *goal_pos)
7258 {
7259 maybe_avoid_player(Pl_objp, goal_pos);
7260 ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
7261
7262 if (aip->ai_flags[AI::AI_Flags::Avoiding_small_ship]) {
7263 ai_turn_towards_vector(&aip->avoid_goal_point, objp, nullptr, nullptr, 0.0f, 0);
7264 accelerate_ship(aip, 0.5f);
7265 return 1;
7266 }
7267
7268 return 0;
7269 }
7270
7271 // Determine if a cylinder of width radius from p0 to p1 will collide with big_objp.
7272 // If so, stuff *collision_point.
will_collide_pp(vec3d * p0,vec3d * p1,float radius,object * big_objp,vec3d * collision_point)7273 int will_collide_pp(vec3d *p0, vec3d *p1, float radius, object *big_objp, vec3d *collision_point)
7274 {
7275 mc_info mc;
7276 mc_info_init(&mc);
7277
7278 polymodel *pm = model_get(Ship_info[Ships[big_objp->instance].ship_info_index].model_num);
7279
7280 mc.model_instance_num = -1;
7281 mc.model_num = pm->id; // Fill in the model to check
7282 mc.orient = &big_objp->orient; // The object's orient
7283 mc.pos = &big_objp->pos; // The object's position
7284 mc.p0 = p0; // Point 1 of ray to check
7285 mc.p1 = p1;
7286 mc.flags = MC_CHECK_MODEL | MC_CHECK_SPHERELINE | MC_SUBMODEL; // flags
7287
7288 mc.radius = radius;
7289
7290 // Only check the 2nd lowest hull object
7291 mc.submodel_num = pm->detail[0];
7292 model_collide(&mc);
7293
7294 if (mc.num_hits)
7295 *collision_point = mc.hit_point_world;
7296
7297 return mc.num_hits;
7298 }
7299
7300 // Return true/false if *objp will collide with *big_objp
7301 // Stuff distance in *distance to collision point if *objp will collide with *big_objp within delta_time seconds.
7302 // Global collision point stuffed in *collision_point
will_collide_with_big_ship(object * objp,vec3d * goal_point,object * big_objp,vec3d * collision_point,float delta_time)7303 int will_collide_with_big_ship(object *objp, vec3d *goal_point, object *big_objp, vec3d *collision_point, float delta_time)
7304 {
7305 float radius;
7306 vec3d end_pos;
7307
7308 radius = big_objp->radius + delta_time * objp->phys_info.speed;
7309
7310 if (vm_vec_dist_quick(&big_objp->pos, &objp->pos) > radius) {
7311 return 0;
7312 }
7313
7314 if (goal_point == NULL) {
7315 vm_vec_scale_add(&end_pos, &objp->pos, &objp->phys_info.vel, delta_time); // Point 2 of ray to check
7316 } else {
7317 end_pos = *goal_point;
7318 }
7319
7320 return will_collide_pp(&objp->pos, &end_pos, objp->radius, big_objp, collision_point);
7321 }
7322
7323 // Return true if *objp is expected to collide with a large ship.
7324 // Stuff global collision point in *collision_point.
7325 // If *goal_point is not NULL, use that as the point towards which *objp will be flying. Don't use *objp velocity
7326 // *ignore_objp will typically be the target this ship is pursuing, either to attack or guard. We don't want to avoid it.
will_collide_with_big_ship_all(object * objp,object * ignore_objp,vec3d * goal_point,vec3d * collision_point,float * distance,float delta_time)7327 int will_collide_with_big_ship_all(object *objp, object *ignore_objp, vec3d *goal_point, vec3d *collision_point, float *distance, float delta_time)
7328 {
7329 ship_obj *so;
7330 object *big_objp;
7331 int collision_obj_index = -1;
7332 float min_dist = 999999.9f;
7333
7334 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
7335 big_objp = &Objects[so->objnum];
7336
7337 if (big_objp == ignore_objp)
7338 continue;
7339
7340 if (Ship_info[Ships[big_objp->instance].ship_info_index].is_big_or_huge()) {
7341 vec3d cur_collision_point;
7342 float cur_dist;
7343
7344 if (will_collide_with_big_ship(objp, goal_point, big_objp, &cur_collision_point, delta_time)) {
7345
7346 cur_dist = vm_vec_dist(&cur_collision_point, &objp->pos);
7347
7348 if (cur_dist < min_dist) {
7349 min_dist = cur_dist;
7350 *collision_point = cur_collision_point;
7351 collision_obj_index = OBJ_INDEX(big_objp);
7352 }
7353 }
7354 }
7355 }
7356
7357 *distance = min_dist;
7358 return collision_obj_index;
7359
7360 }
7361
7362 typedef struct {
7363 float dist;
7364 int collide;
7365 vec3d pos;
7366 } sgoal;
7367
7368 // Pick a point for *objp to fly towards to avoid a collision with *big_objp at *collision_point
7369 // Return result in *avoid_pos
mabs_pick_goal_point(object * objp,object * big_objp,vec3d * collision_point,vec3d * avoid_pos)7370 void mabs_pick_goal_point(object *objp, object *big_objp, vec3d *collision_point, vec3d *avoid_pos)
7371 {
7372 matrix mat1;
7373 sgoal goals[4];
7374 vec3d v2b;
7375
7376 vm_vec_normalized_dir(&v2b, collision_point, &objp->pos);
7377 vm_vector_2_matrix(&mat1, &v2b, NULL, NULL);
7378
7379 int found = 0;
7380
7381 // Try various scales, in 0.5f, 0.75f, 1.0f, 1.25f.
7382 // First try 0.5f to see if we can find a point that near the center of the target ship, which presumably
7383 // means less of a turn.
7384 // Try going as far as 1.25f * radius.
7385 float s;
7386 for (s=0.5f; s<1.3f; s += 0.25f) {
7387 int i;
7388 for (i=0; i<4; i++) {
7389 vec3d p = big_objp->pos;
7390 float ku = big_objp->radius*s + objp->radius * (OBJ_INDEX(objp) % 4)/4; // This objp->radius stuff to prevent ships from glomming together at one point
7391 float kr = big_objp->radius*s + objp->radius * ((OBJ_INDEX(objp) % 4) ^ 2)/4;
7392 if (i&1)
7393 ku = -ku;
7394 if (i&2)
7395 kr = -kr;
7396 vm_vec_scale_add2(&p, &mat1.vec.uvec, ku);
7397 vm_vec_scale_add2(&p, &mat1.vec.rvec, kr);
7398 goals[i].pos = p;
7399 goals[i].dist = vm_vec_dist_quick(&objp->pos, &p);
7400 goals[i].collide = will_collide_pp(&objp->pos, &p, objp->radius, big_objp, collision_point);
7401 if (!goals[i].collide)
7402 found = 1;
7403 }
7404
7405 // If we found a point that doesn't collide, find the nearest one and make that the *avoid_pos.
7406 if (found) {
7407 float min_dist = 9999999.9f;
7408 int min_index = -1;
7409
7410 for (i=0; i<4; i++) {
7411 if (!goals[i].collide && (goals[i].dist < min_dist)) {
7412 min_dist = goals[i].dist;
7413 min_index = i;
7414 }
7415 }
7416
7417 Assert(i != -1);
7418 if (i != -1) {
7419 *avoid_pos = goals[min_index].pos;
7420 return;
7421 }
7422 }
7423 }
7424
7425 // Drat. We tried and tried and could not find a point that did not cause a collision.
7426 // Get this dump pilot far away from the problem ship.
7427 vec3d away_vec;
7428 vm_vec_normalized_dir(&away_vec, &objp->pos, collision_point);
7429 vm_vec_scale_add(avoid_pos, &objp->pos, &away_vec, big_objp->radius*1.5f);
7430
7431 }
7432
7433 /**
7434 * Return true if a large ship is being ignored.
7435 */
maybe_avoid_big_ship(object * objp,object * ignore_objp,ai_info * aip,vec3d * goal_point,float delta_time,float time_scale=1.f)7436 int maybe_avoid_big_ship(object *objp, object *ignore_objp, ai_info *aip, vec3d *goal_point, float delta_time, float time_scale = 1.f)
7437 {
7438 if (timestamp_elapsed(aip->avoid_check_timestamp)) {
7439 float distance;
7440 vec3d collision_point;
7441 int ship_num;
7442 int next_check_time;
7443 if ((ship_num = will_collide_with_big_ship_all(Pl_objp, ignore_objp, goal_point, &collision_point, &distance, delta_time)) != -1) {
7444 aip->ai_flags.set(AI::AI_Flags::Avoiding_big_ship);
7445 mabs_pick_goal_point(objp, &Objects[ship_num], &collision_point, &aip->avoid_goal_point);
7446 float dist = vm_vec_dist_quick(&aip->avoid_goal_point, &objp->pos);
7447 next_check_time = (int) (2000 + MIN(1000, (dist * 2.0f)) * time_scale); // Delay until check again is based on distance to avoid point.
7448 aip->avoid_check_timestamp = timestamp(next_check_time);
7449 aip->avoid_ship_num = ship_num;
7450 } else {
7451 aip->ai_flags.remove(AI::AI_Flags::Avoiding_big_ship);
7452 aip->ai_flags.remove(AI::AI_Flags::Avoiding_small_ship);
7453 aip->avoid_ship_num = -1;
7454 next_check_time = (int) (1500 * time_scale);
7455 aip->avoid_check_timestamp = timestamp(1500);
7456 }
7457 }
7458
7459 if (aip->ai_flags[AI::AI_Flags::Avoiding_big_ship]) {
7460 vec3d v2g;
7461
7462 ai_turn_towards_vector(&aip->avoid_goal_point, Pl_objp, nullptr, nullptr, 0.0f, 0);
7463 vm_vec_normalized_dir(&v2g, &aip->avoid_goal_point, &Pl_objp->pos);
7464 float dot = vm_vec_dot(&objp->orient.vec.fvec, &v2g);
7465 float d2 = (1.0f + dot) * (1.0f + dot);
7466 accelerate_ship(aip, d2/4.0f);
7467 return 1;
7468 }
7469
7470 return 0;
7471 }
7472
7473 /**
7474 * Set desired right vector for ships flying towards another ship.
7475 * Since this is governed only by vector to target, it causes ships to align bank and look less chaotic.
7476 */
compute_desired_rvec(vec3d * rvec,vec3d * goal_pos,vec3d * cur_pos)7477 void compute_desired_rvec(vec3d *rvec, vec3d *goal_pos, vec3d *cur_pos)
7478 {
7479 vec3d v2e;
7480
7481 vm_vec_normalized_dir(&v2e, goal_pos, cur_pos);
7482 rvec->xyz.x = v2e.xyz.z;
7483 rvec->xyz.y = 0.0f;
7484 rvec->xyz.z = -v2e.xyz.x;
7485 if (vm_vec_mag_squared(rvec) < 0.001f)
7486 rvec->xyz.y = 1.0f;
7487 }
7488
7489 /**
7490 * Handler for stealth find submode of Chase.
7491 */
ai_stealth_find()7492 void ai_stealth_find()
7493 {
7494 ai_info *aip;
7495 ship_info *sip;
7496
7497 vec3d new_pos, vec_to_enemy;
7498 float dist_to_enemy, dot_to_enemy, dot_from_enemy;
7499
7500 Assert(Ships[Pl_objp->instance].ship_info_index >= 0);
7501 sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
7502 Assert(Ships[Pl_objp->instance].ai_index >= 0);
7503 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
7504
7505 // get time since last seen
7506 int delta_time = (timestamp() - aip->stealth_last_visible_stamp);
7507
7508 // if delta_time is really big, i'm real confused, start sweep
7509 if (delta_time > 10000) {
7510 aip->submode_parm0 = SM_SF_BAIL;
7511 }
7512
7513 // guestimate new position
7514 vm_vec_scale_add(&new_pos, &aip->stealth_last_pos, &aip->stealth_velocity, (delta_time * 0.001f));
7515
7516 // if I think he's behind me, go to the goal point
7517 if ( aip->submode_parm0 == SM_SF_BEHIND ) {
7518 new_pos = aip->goal_point;
7519 }
7520
7521 // check for collision with big ships
7522 if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &new_pos, 10.0f)) {
7523 // reset ai submode to chase
7524 return;
7525 }
7526
7527 // if dist is near max and dot is close to 1, accel, afterburn
7528 vm_vec_sub(&vec_to_enemy, &new_pos, &Pl_objp->pos);
7529 dist_to_enemy = vm_vec_normalize_quick(&vec_to_enemy);
7530 dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
7531
7532 // if i think i should see him ahead and i don't, set goal pos and turn around, but only if I haven't seen him for a while
7533 if ( (delta_time > 800) && (aip->submode_parm0 == SM_SF_AHEAD) && (dot_to_enemy > .94) && (dist_to_enemy < get_skill_stealth_dist_scaler()*STEALTH_MAX_VIEW_DIST + 50) ) {
7534 // do turn around)
7535 vm_vec_scale_add(&aip->goal_point, &Pl_objp->pos, &Pl_objp->orient.vec.fvec, -300.0f);
7536 aip->submode_parm0 = SM_SF_BEHIND;
7537 vm_vec_sub(&vec_to_enemy, &new_pos, &Pl_objp->pos);
7538 dist_to_enemy = vm_vec_normalize_quick(&vec_to_enemy);
7539 dot_to_enemy = vm_vec_dot(&vec_to_enemy, &Pl_objp->orient.vec.fvec);
7540 }
7541
7542 if ( (dist_to_enemy > get_skill_stealth_dist_scaler()*STEALTH_MAX_VIEW_DIST) && (dot_to_enemy > 0.94f) ) { // 20 degree half angle
7543 // accelerate ship
7544 accelerate_ship(aip, 1.0f);
7545
7546 // engage afterburner
7547 if (!( Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
7548 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
7549 afterburners_start(Pl_objp);
7550 aip->afterburner_stop_time = Missiontime + 3*F1_0/2;
7551 }
7552 }
7553
7554 ai_turn_towards_vector(&new_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
7555 return;
7556 }
7557
7558 // If enemy more than 500 meters away, all ships flying there will tend to match bank.
7559 // They do this by using their vector to their target to compute their right vector and causing ai_turn_towards_vector
7560 // to interpolate a matrix rather than just a vector.
7561 if (dist_to_enemy > 500.0f) {
7562 vec3d rvec;
7563 compute_desired_rvec(&rvec, &new_pos, &Pl_objp->pos);
7564 ai_turn_towards_vector(&new_pos, Pl_objp, nullptr, nullptr, 0.0f, 0, &rvec);
7565 } else {
7566 ai_turn_towards_vector(&new_pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
7567 }
7568
7569 dot_from_enemy = -vm_vec_dot(&vec_to_enemy, &En_objp->orient.vec.fvec);
7570
7571 attack_set_accel(aip, sip, dist_to_enemy, dot_to_enemy, dot_from_enemy);
7572 }
7573
7574 /**
7575 * Try to find stealth ship by sweeping an area
7576 */
ai_stealth_sweep()7577 void ai_stealth_sweep()
7578 {
7579 ai_info *aip;
7580
7581 Assert(Ships[Pl_objp->instance].ai_index >= 0);
7582 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
7583
7584 vec3d goal_pt;
7585 vec3d forward, right, up;
7586 int lost_time;
7587
7588 // time since stealth last seen
7589 lost_time = (timestamp() - aip->stealth_last_visible_stamp);
7590
7591 // determine which pt to fly to in sweep by keeping track of parm0
7592 if (aip->submode_parm0 == SM_SS_SET_GOAL) {
7593
7594 // don't make goal pt more than 2k from current pos
7595 vm_vec_scale_add(&goal_pt, &aip->stealth_last_pos, &aip->stealth_velocity, (0.001f * lost_time));
7596
7597 // make box size based on speed of stealth and expected time to intercept (keep box in range 200-500)
7598 float box_size = vm_vec_mag_quick(&aip->stealth_velocity) * (0.001f * lost_time);
7599 box_size = MIN(200.0f, box_size);
7600 box_size = MAX(500.0f, box_size);
7601 aip->stealth_sweep_box_size = box_size;
7602
7603 aip->goal_point = goal_pt;
7604 aip->submode_parm0 = SM_SS_BOX0;
7605 }
7606
7607 // GET UP, RIGHT, FORWARD FOR BOX based on stealth ship's velocity
7608 // if velocity changes in stealth mode, then ship is *seen*, and falls out of sweep mode
7609 // if stealth has no velocity make a velocity
7610 if ( vm_vec_mag_quick(&aip->stealth_velocity) < 1 ) {
7611 vm_vec_rand_vec_quick(&aip->stealth_velocity);
7612 }
7613
7614 // get "right" vector for box
7615 vm_vec_cross(&right, &aip->stealth_velocity, &vmd_y_vector);
7616
7617 if ( vm_vec_mag_quick(&right) < 0.01 ) {
7618 vm_vec_cross(&right, &aip->stealth_velocity, &vmd_z_vector);
7619 }
7620
7621 vm_vec_normalize_quick(&right);
7622
7623 // get "forward" for box
7624 vm_vec_copy_normalize_quick(&forward, &aip->stealth_velocity);
7625
7626 // get "up" for box
7627 vm_vec_cross(&up, &forward, &right);
7628
7629 // lost far away ahead (do box)
7630 switch(aip->submode_parm0) {
7631 case SM_SS_BOX0:
7632 goal_pt = aip->goal_point;
7633 break;
7634
7635 // pt1 -U +R
7636 case SM_SS_LR:
7637 vm_vec_scale_add(&goal_pt, &aip->goal_point, &up, -aip->stealth_sweep_box_size);
7638 vm_vec_scale_add2(&goal_pt, &right, aip->stealth_sweep_box_size);
7639 vm_vec_scale_add2(&goal_pt, &forward, 0.5f*aip->stealth_sweep_box_size);
7640 break;
7641
7642 // pt2 +U -R
7643 case SM_SS_UL:
7644 vm_vec_scale_add(&goal_pt, &aip->goal_point, &up, aip->stealth_sweep_box_size);
7645 vm_vec_scale_add2(&goal_pt, &right, -aip->stealth_sweep_box_size);
7646 vm_vec_scale_add2(&goal_pt, &forward, 0.5f*aip->stealth_sweep_box_size);
7647 break;
7648
7649 // pt3 back
7650 case SM_SS_BOX1:
7651 goal_pt = aip->goal_point;
7652 break;
7653
7654 // pt4 +U +R
7655 case SM_SS_UR:
7656 vm_vec_scale_add(&goal_pt, &aip->goal_point, &up, aip->stealth_sweep_box_size);
7657 vm_vec_scale_add2(&goal_pt, &right, aip->stealth_sweep_box_size);
7658 vm_vec_scale_add2(&goal_pt, &forward, 0.5f*aip->stealth_sweep_box_size);
7659 break;
7660
7661 // pt5 -U -R
7662 case SM_SS_LL:
7663 vm_vec_scale_add(&goal_pt, &aip->goal_point, &up, -aip->stealth_sweep_box_size);
7664 vm_vec_scale_add2(&goal_pt, &right, -aip->stealth_sweep_box_size);
7665 vm_vec_scale_add2(&goal_pt, &forward, 0.5f*aip->stealth_sweep_box_size);
7666 break;
7667
7668 // pt6 back
7669 case SM_SS_BOX2:
7670 goal_pt = aip->goal_point;
7671 break;
7672
7673 default:
7674 Int3();
7675
7676 }
7677
7678 // when close to goal_pt, update next goal pt
7679 float dist_to_goal = vm_vec_dist(&goal_pt, &Pl_objp->pos);
7680 if (dist_to_goal < 15) {
7681 aip->submode_parm0++;
7682 }
7683
7684 // check for collision with big ship
7685 if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &goal_pt, 10.0f)) {
7686 // skip to the next pt on box
7687 aip->submode_parm0++;
7688 return;
7689 }
7690
7691 ai_turn_towards_vector(&goal_pt, Pl_objp, nullptr, nullptr, 0.0f, 0);
7692
7693 float dot = 1.0f;
7694 if (dist_to_goal < 100) {
7695 vec3d vec_to_goal;
7696 vm_vec_normalized_dir(&vec_to_goal, &goal_pt, &Pl_objp->pos);
7697 dot = vm_vec_dot(&vec_to_goal, &Pl_objp->orient.vec.fvec);
7698 }
7699
7700 accelerate_ship(aip, 0.8f*dot);
7701 }
7702
7703 /**
7704 * ATTACK submode handler for chase mode.
7705 */
ai_chase_attack(ai_info * aip,ship_info * sip,vec3d * predicted_enemy_pos,float dist_to_enemy,int modelnum)7706 void ai_chase_attack(ai_info *aip, ship_info *sip, vec3d *predicted_enemy_pos, float dist_to_enemy, int modelnum)
7707 {
7708 int start_bank;
7709 float dot_to_enemy, dot_from_enemy;
7710 float bank_override = 0.0f;
7711
7712 if (!(aip->ai_profile_flags[AI::Profile_Flags::No_special_player_avoid]) && avoid_player(Pl_objp, predicted_enemy_pos))
7713 return;
7714
7715 compute_dots(Pl_objp, En_objp, &dot_to_enemy, &dot_from_enemy);
7716
7717 polymodel *po = model_get( modelnum );
7718
7719 vec3d *rel_pos;
7720 float scale;
7721 vec3d randvec;
7722 vec3d new_pos;
7723
7724 start_bank = Ships[aip->shipnum].weapons.current_primary_bank;
7725 if (po->n_guns && start_bank != -1 && !(The_mission.ai_profile->flags[AI::Profile_Flags::Ai_aims_from_ship_center])) {
7726 rel_pos = &po->gun_banks[start_bank].pnt[0];
7727 } else
7728 rel_pos = NULL;
7729
7730 // If ship moving slowly relative to its size, then don't attack its center point.
7731 // How far from center we attack is based on speed, size and distance to enemy
7732 if (En_objp->radius > En_objp->phys_info.speed) {
7733 static_randvec(OBJ_INDEX(Pl_objp), &randvec);
7734 scale = dist_to_enemy/(dist_to_enemy + En_objp->radius) * En_objp->radius;
7735 scale *= 0.5f * En_objp->radius/(En_objp->phys_info.speed + En_objp->radius); // scale downward by 1/2 to 1/4
7736 vm_vec_scale_add(&new_pos, predicted_enemy_pos, &randvec, scale);
7737 } else
7738 new_pos = *predicted_enemy_pos;
7739
7740 //SUSHI: Don't change bank while circle strafing or glide attacking
7741 if (dist_to_enemy < 250.0f && dot_from_enemy > 0.7f && aip->submode != AIS_CHASE_CIRCLESTRAFE && aip->submode != AIS_CHASE_GLIDEATTACK) {
7742 bank_override = Pl_objp->phys_info.speed;
7743 }
7744
7745 // If enemy more than 500 meters away, all ships flying there will tend to match bank.
7746 // They do this by using their vector to their target to compute their right vector and causing ai_turn_towards_vector
7747 // to interpolate a matrix rather than just a vector.
7748 if (dist_to_enemy > 500.0f) {
7749 vec3d rvec;
7750 compute_desired_rvec(&rvec, predicted_enemy_pos, &Pl_objp->pos);
7751 ai_turn_towards_vector(&new_pos, Pl_objp, nullptr, rel_pos, bank_override, 0, &rvec);
7752 } else {
7753 ai_turn_towards_vector(&new_pos, Pl_objp, nullptr, rel_pos, bank_override, 0);
7754 }
7755
7756 attack_set_accel(aip, sip, dist_to_enemy, dot_to_enemy, dot_from_enemy);
7757 }
7758
7759 // EVADE_SQUIGGLE submode handler for chase mode.
7760 // Changed by MK on 5/5/97.
7761 // Used to evade towards a point off the right or up vector.
7762 // Now, evade straight away to try to get far away.
7763 // The squiggling should protect against laser fire.
ai_chase_es(ai_info * aip)7764 void ai_chase_es(ai_info *aip)
7765 {
7766 vec3d tvec;
7767 fix timeslice;
7768 fix scale;
7769
7770 tvec = Pl_objp->pos;
7771
7772 timeslice = (Missiontime >> 16) & 0x0f;
7773 scale = ((Missiontime >> 16) & 0x0f) << 14;
7774
7775 if (timeslice & 0x01)
7776 vm_vec_scale_add2(&tvec, &Pl_objp->orient.vec.rvec, f2fl(scale ^ 0x10000));
7777 if (timeslice & 0x02)
7778 vm_vec_scale_sub2(&tvec, &Pl_objp->orient.vec.rvec, f2fl(scale));
7779 if (timeslice & 0x04)
7780 vm_vec_scale_add2(&tvec, &Pl_objp->orient.vec.uvec, f2fl(scale ^ 0x10000));
7781 if (timeslice & 0x08)
7782 vm_vec_scale_sub2(&tvec, &Pl_objp->orient.vec.uvec, f2fl(scale));
7783
7784 while (vm_vec_dist_quick(&tvec, &Pl_objp->pos) < 0.1f) {
7785 tvec.xyz.x += frand();
7786 tvec.xyz.y += frand();
7787 }
7788
7789 float bank_override = Pl_objp->phys_info.speed;
7790
7791 ai_turn_towards_vector(&tvec, Pl_objp, nullptr, nullptr, bank_override, 0);
7792 accelerate_ship(aip, 1.0f);
7793 }
7794
7795 /**
7796 * Trying to get away from opponent.
7797 */
ai_chase_ga(ai_info * aip,ship_info * sip)7798 void ai_chase_ga(ai_info *aip, ship_info *sip)
7799 {
7800 // If not near end of this submode, evade squiggly. If near end, just fly straight for a bit
7801 vec3d tvec;
7802 float bank_override;
7803 vec3d vec_from_enemy;
7804
7805 if (En_objp != NULL) {
7806 vm_vec_normalized_dir(&vec_from_enemy, &Pl_objp->pos, &En_objp->pos);
7807 } else
7808 vec_from_enemy = Pl_objp->orient.vec.fvec;
7809
7810 static_randvec(Missiontime >> 15, &tvec);
7811 vm_vec_scale(&tvec, 100.0f);
7812 vm_vec_scale_add2(&tvec, &vec_from_enemy, 300.0f);
7813 vm_vec_add2(&tvec, &Pl_objp->pos);
7814
7815 bank_override = Pl_objp->phys_info.speed;
7816
7817 ai_turn_towards_vector(&tvec, Pl_objp, nullptr, nullptr, bank_override, 0);
7818
7819 accelerate_ship(aip, 2.0f);
7820
7821 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
7822 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON )) {
7823 float percent_left = 100.0f * Ships[Pl_objp->instance].afterburner_fuel / sip->afterburner_fuel_capacity;
7824 if (percent_left > 30.0f + ((OBJ_INDEX(Pl_objp)) & 0x0f)) {
7825 afterburners_start(Pl_objp);
7826 aip->afterburner_stop_time = Missiontime + 3*F1_0/2;
7827 }
7828 afterburners_start(Pl_objp);
7829 aip->afterburner_stop_time = Missiontime + 3*F1_0/2;
7830 }
7831 }
7832
7833 }
7834
7835 // Make object *objp attack subsystem with ID = subnum.
7836 // Return true if found a subsystem to attack, else return false.
7837 // Note, can fail if subsystem exists, but has no hits.
ai_set_attack_subsystem(object * objp,int subnum)7838 int ai_set_attack_subsystem(object *objp, int subnum)
7839 {
7840 int temp;
7841 ship *shipp, *attacker_shipp;
7842 ai_info *aip;
7843 ship_subsys *ssp;
7844 object *attacked_objp;
7845
7846 Assert(objp->type == OBJ_SHIP);
7847 Assert(objp->instance >= 0);
7848
7849 attacker_shipp = &Ships[objp->instance];
7850 Assert(attacker_shipp->ai_index >= 0);
7851
7852 aip = &Ai_info[attacker_shipp->ai_index];
7853
7854 // MWA -- 2/27/98. Due to AL's changes, target_objnum is now not always valid (at least sometimes
7855 // in terms of goals). So, bail if we don't have a valid target.
7856 if ( aip->target_objnum == -1 )
7857 return 0;
7858
7859 Assertion(aip->target_objnum >= 0, "The target_objnum for %s has become a nonsense value of %d. Please report!",attacker_shipp->ship_name ,aip->target_objnum);
7860
7861 attacked_objp = &Objects[aip->target_objnum];
7862 shipp = &Ships[attacked_objp->instance]; // need to get our target's ship pointer!!!
7863
7864 ssp = ship_get_indexed_subsys(shipp, subnum, &objp->pos);
7865 if (ssp == NULL)
7866 return 0;
7867
7868 set_targeted_subsys(aip, ssp, aip->target_objnum);
7869
7870 if (aip->ignore_objnum == aip->target_objnum)
7871 aip->ignore_objnum = UNUSED_OBJNUM;
7872
7873 // Goober5000
7874 temp = find_ignore_new_object_index(aip, aip->target_objnum);
7875 if (temp >= 0)
7876 {
7877 aip->ignore_new_objnums[temp] = UNUSED_OBJNUM;
7878 }
7879 else if (is_ignore_object(aip, aip->target_objnum, 1))
7880 {
7881 aip->ignore_objnum = UNUSED_OBJNUM;
7882 }
7883
7884 // -- Done at caller in ai_process_mission_orders -- attacked_objp->flags .add(Object::Object_Flags::Protected);
7885
7886 ai_set_goal_abort_support_call(objp, aip);
7887 aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME);
7888
7889 return 1;
7890 }
7891
ai_set_guard_vec(object * objp,object * guard_objp)7892 void ai_set_guard_vec(object *objp, object *guard_objp)
7893 {
7894 ai_info *aip;
7895 float radius;
7896
7897 aip = &Ai_info[Ships[objp->instance].ai_index];
7898
7899 // Handle case of bogus call in which ship is told to guard self.
7900 Assert(objp != guard_objp);
7901 if (objp == guard_objp) {
7902 vm_vec_rand_vec_quick(&aip->guard_vec);
7903 vm_vec_scale(&aip->guard_vec, 100.0f);
7904 return;
7905 }
7906
7907 // check if guard_objp is BIG
7908 radius = 5.0f * (objp->radius + guard_objp->radius) + 50.0f;
7909 if (radius > 300.0f) {
7910 radius = guard_objp->radius * 1.25f;
7911 }
7912
7913 vm_vec_sub(&aip->guard_vec, &objp->pos, &guard_objp->pos);
7914
7915 if (vm_vec_mag(&aip->guard_vec) > 3.0f*radius) {
7916 // Far away, don't just use vector to object, causes clustering of guard ships.
7917 vec3d tvec, rvec;
7918 float mag;
7919 mag = vm_vec_copy_normalize(&tvec, &aip->guard_vec);
7920 vm_vec_rand_vec_quick(&rvec);
7921 vm_vec_scale_add2(&tvec, &rvec, 0.5f);
7922 vm_vec_copy_scale(&aip->guard_vec, &tvec, mag);
7923 }
7924
7925 vm_vec_normalize_quick(&aip->guard_vec);
7926 vm_vec_scale(&aip->guard_vec, radius);
7927 }
7928
7929 // Make object *objp guard object *other_objp.
7930 // To be called from the goals code.
ai_set_guard_wing(object * objp,int wingnum)7931 void ai_set_guard_wing(object *objp, int wingnum)
7932 {
7933 ship *shipp;
7934 ai_info *aip;
7935 int leader_objnum, leader_shipnum;
7936
7937 Assert(wingnum >= 0);
7938
7939 Assert(objp->type == OBJ_SHIP);
7940 Assert(objp->instance >= 0);
7941
7942 // shouldn't set the ai mode for the player
7943 if ( objp == Player_obj ) {
7944 return;
7945 }
7946
7947 shipp = &Ships[objp->instance];
7948
7949 Assert(shipp->ai_index >= 0);
7950
7951 aip = &Ai_info[shipp->ai_index];
7952 force_avoid_player_check(objp, aip);
7953
7954 ai_set_goal_abort_support_call(objp, aip);
7955 aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME);
7956
7957 // This function is called whenever a guarded ship is destroyed, so this code
7958 // prevents a ship from trying to guard a non-existent wing.
7959 if (Wings[wingnum].current_count < 1) {
7960 aip->guard_objnum = -1;
7961 aip->guard_wingnum = -1;
7962 aip->mode = AIM_NONE;
7963 } else {
7964 leader_shipnum = Wings[wingnum].ship_index[0];
7965 leader_objnum = Ships[leader_shipnum].objnum;
7966
7967 Assert((leader_objnum >= 0) && (leader_objnum < MAX_OBJECTS));
7968
7969 if (leader_objnum == OBJ_INDEX(objp)) {
7970 return;
7971 }
7972
7973 aip->guard_wingnum = wingnum;
7974 aip->guard_objnum = leader_objnum;
7975 aip->guard_signature = Objects[leader_objnum].signature;
7976 aip->mode = AIM_GUARD;
7977 aip->submode = AIS_GUARD_STATIC;
7978 aip->submode_start_time = Missiontime;
7979
7980 ai_set_guard_vec(objp, &Objects[leader_objnum]);
7981 }
7982 }
7983
7984 // Make objp guard other_objp
7985 // If other_objp is a member of a wing, objp will guard that whole wing
7986 // UNLESS objp is also a member of the wing!
ai_set_guard_object(object * objp,object * other_objp)7987 void ai_set_guard_object(object *objp, object *other_objp)
7988 {
7989 ship *shipp;
7990 ai_info *aip;
7991 int other_objnum;
7992
7993 Assert(objp->type == OBJ_SHIP);
7994 Assert(objp->instance >= 0);
7995 Assert(objp != other_objp);
7996
7997 shipp = &Ships[objp->instance];
7998
7999 Assert(shipp->ai_index >= 0);
8000
8001 aip = &Ai_info[shipp->ai_index];
8002 aip->avoid_check_timestamp = timestamp(1);
8003
8004 // If ship to guard is in a wing, guard that whole wing, unless the appropriate flag has been set
8005 ai_info *other_aip = &Ai_info[Ships[other_objp->instance].ai_index];
8006 if ((other_aip->wing != -1) && (other_aip->wing != aip->wing) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Ai_guards_specific_ship_in_wing])) {
8007 ai_set_guard_wing(objp, Ai_info[Ships[other_objp->instance].ai_index].wing);
8008 } else {
8009
8010 other_objnum = OBJ_INDEX(other_objp);
8011
8012 aip->guard_objnum = other_objnum;
8013 aip->guard_signature = other_objp->signature;
8014 aip->guard_wingnum = -1;
8015
8016 aip->mode = AIM_GUARD;
8017 aip->submode = AIS_GUARD_STATIC;
8018 aip->submode_start_time = Missiontime;
8019
8020 Assert(other_objnum >= 0); // Hmm, bogus object and we need its position for guard_vec.
8021
8022 ai_set_guard_vec(objp, &Objects[other_objnum]);
8023
8024 ai_set_goal_abort_support_call(objp, aip);
8025 aip->ok_to_target_timestamp = timestamp(DELAY_TARGET_TIME);
8026 }
8027 }
8028
8029 // Update the aspect_locked_time field based on whether enemy is in view cone.
8030 // Also set/clear AIF_SEEK_LOCK.
update_aspect_lock_information(ai_info * aip,vec3d * vec_to_enemy,float dist_to_enemy,float enemy_radius)8031 void update_aspect_lock_information(ai_info *aip, vec3d *vec_to_enemy, float dist_to_enemy, float enemy_radius)
8032 {
8033 float dot_to_enemy;
8034 int num_weapon_types;
8035 int weapon_id_list[MAX_WEAPON_TYPES], weapon_bank_list[MAX_WEAPON_TYPES];
8036 ship *shipp;
8037 ship *tshpp;
8038 ship_weapon *swp;
8039 weapon_info *wip;
8040 object *tobjp = &Objects[aip->target_objnum];
8041
8042 shipp = &Ships[aip->shipnum];
8043 tshpp = NULL;
8044 swp = &shipp->weapons;
8045
8046 object *aiobjp = &Objects[shipp->objnum];
8047
8048 // AL 3-7-98: This probably should never happen, but check to ensure that current_secondary_bank is valid
8049 if ( (swp->current_secondary_bank < 0) || (swp->current_secondary_bank >= swp->num_secondary_banks) ) {
8050 return;
8051 }
8052
8053 if (tobjp->type == OBJ_SHIP) {
8054 tshpp = &Ships[tobjp->instance];
8055 }
8056
8057 num_weapon_types = get_available_secondary_weapons(Pl_objp, weapon_id_list, weapon_bank_list);
8058
8059 wip = &Weapon_info[swp->secondary_bank_weapons[swp->current_secondary_bank]];
8060
8061 if (num_weapon_types && (wip->is_locked_homing()) && !(shipp->flags[Ship::Ship_Flags::No_secondary_lockon])) {
8062 aip->ai_flags.set(AI::AI_Flags::Seek_lock, dist_to_enemy > 300.0f - MIN(enemy_radius, 100.0f));
8063
8064 // Update locking information for aspect seeking missiles.
8065 aip->current_target_is_locked = 0;
8066 dot_to_enemy = vm_vec_dot(vec_to_enemy, &aiobjp->orient.vec.fvec);
8067
8068 float needed_dot = 0.9f - 0.5f * enemy_radius/(dist_to_enemy + enemy_radius); // Replaced MIN_TRACKABLE_DOT with 0.9f
8069 if (dot_to_enemy > needed_dot &&
8070 (wip->wi_flags[Weapon::Info_Flags::Homing_aspect] ||
8071 (wip->wi_flags[Weapon::Info_Flags::Homing_javelin] &&
8072 (tshpp == NULL ||
8073 ship_get_closest_subsys_in_sight(tshpp, SUBSYSTEM_ENGINE, &aiobjp->pos))))) {
8074 aip->aspect_locked_time += flFrametime;
8075 if (aip->aspect_locked_time >= wip->min_lock_time) {
8076 aip->current_target_is_locked = 1;
8077 }
8078 } else {
8079 if (aip->aspect_locked_time > wip->min_lock_time)
8080 aip->aspect_locked_time = wip->min_lock_time;
8081
8082 aip->aspect_locked_time -= flFrametime*2;
8083 if (aip->aspect_locked_time < 0.0f)
8084 aip->aspect_locked_time = 0.0f;
8085 }
8086
8087 } else {
8088 aip->current_target_is_locked = 0;
8089 aip->aspect_locked_time = 0.0f; // Used to be this, why?: wip->min_lock_time;
8090 aip->ai_flags.remove(AI::AI_Flags::Seek_lock);
8091 }
8092
8093 }
8094
8095 // We're in chase mode and we've recently collided with our target.
8096 // Fly away from it!
ai_chase_fly_away(object * objp,ai_info * aip)8097 void ai_chase_fly_away(object *objp, ai_info *aip)
8098 {
8099 int abort_flag = 0;
8100
8101 if (aip->ai_flags[AI::AI_Flags::Target_collision]) {
8102 aip->ai_flags.remove(AI::AI_Flags::Target_collision); // Don't process this hit again next frame.
8103 aip->submode = SM_FLY_AWAY; // Focus on avoiding target
8104 aip->submode_start_time = Missiontime;
8105 }
8106
8107 if ((aip->target_objnum == -1) || (Objects[aip->target_objnum].signature != aip->target_signature)) {
8108 abort_flag = 1;
8109 }
8110
8111 if (abort_flag || (Missiontime > aip->submode_start_time + F1_0)) {
8112 aip->last_attack_time = Missiontime;
8113 aip->submode = SM_ATTACK;
8114 aip->submode_start_time = Missiontime;
8115 } else {
8116 vec3d v2e;
8117 float dot;
8118
8119 Assertion(aip->target_objnum >= 0, "This ship's target objnum is nonsense. It is %d instead of a positive number or -1.", aip->target_objnum);
8120
8121 vm_vec_normalized_dir(&v2e, &Objects[aip->target_objnum].pos, &objp->pos);
8122
8123 dot = vm_vec_dot(&objp->orient.vec.fvec, &v2e);
8124 if (dot < 0.0f)
8125 accelerate_ship(aip, 1.0f);
8126 else
8127 accelerate_ship(aip, 1.0f - dot);
8128 turn_away_from_point(objp, &Objects[aip->target_objnum].pos, 0.0f);
8129 }
8130 }
8131
8132 // Return bank index of favored secondary weapon.
8133 // Return -1 if nothing favored.
8134 // "favored" means SEXPs have specified the weapon as being good to fire at en_objp.
has_preferred_secondary(object * objp,object * en_objp,ship_weapon * swp)8135 int has_preferred_secondary(object *objp, object *en_objp, ship_weapon *swp)
8136 {
8137 int i;
8138
8139 for (i=0; i<swp->num_secondary_banks; i++) {
8140 if (swp->secondary_bank_capacity[i] > 0) {
8141 if (swp->secondary_bank_ammo[i] > 0) {
8142 if (is_preferred_weapon(swp->secondary_bank_weapons[i], objp, en_objp) != -1){
8143 return i;
8144 }
8145 }
8146 }
8147 }
8148
8149 return -1;
8150 }
8151
8152 // Choose which secondary weapon to fire.
8153 // Note, this is not like ai_select_secondary_weapon(). "choose" means make a choice.
8154 // "select" means execute an order. Get it?
8155 // This function calls ai_select_secondary_weapon() with the characteristics it should search for.
ai_choose_secondary_weapon(object * objp,ai_info * aip,object * en_objp)8156 void ai_choose_secondary_weapon(object *objp, ai_info *aip, object *en_objp)
8157 {
8158 float subsystem_strength = 0.0f;
8159 bool is_big_ship;
8160 flagset<Weapon::Info_Flags> wif_priority1, wif_priority2;
8161 ship_weapon *swp;
8162 ship_info *esip;
8163
8164 if ( en_objp->type == OBJ_SHIP ) {
8165 esip = &Ship_info[Ships[en_objp->instance].ship_info_index];
8166 } else {
8167 esip = NULL;
8168 }
8169
8170 swp = &Ships[objp->instance].weapons;
8171
8172 // AL 3-5-98: do a quick out if the ship has no secondaries
8173 if ( swp->num_secondary_banks <= 0 ) {
8174 swp->current_secondary_bank = -1;
8175 return;
8176 }
8177
8178 int preferred_secondary = has_preferred_secondary(objp, en_objp, swp);
8179
8180 if (preferred_secondary != -1) {
8181 if (swp->current_secondary_bank != preferred_secondary) {
8182 aip->current_target_is_locked = 0;
8183 aip->aspect_locked_time = 0.0f;
8184 swp->current_secondary_bank = preferred_secondary;
8185 }
8186 aip->ai_flags.set(AI::AI_Flags::Unload_secondaries);
8187 } else {
8188 aip->ai_flags.remove(AI::AI_Flags::Unload_secondaries);
8189 if (aip->targeted_subsys) {
8190 subsystem_strength = aip->targeted_subsys->current_hits;
8191 }
8192
8193 if ( esip ) {
8194 is_big_ship = esip->class_type >= 0 && Ship_types[esip->class_type].flags[Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only];
8195 } else {
8196 is_big_ship = false;
8197 }
8198
8199 if (is_big_ship)
8200 {
8201 wif_priority1.set(Weapon::Info_Flags::Huge).set(Weapon::Info_Flags::Capital_plus);
8202 if (aip->ai_profile_flags[AI::Profile_Flags::Smart_secondary_weapon_selection]) {
8203 wif_priority2.set(Weapon::Info_Flags::Bomber_plus);
8204 }
8205 else {
8206 wif_priority2.set(Weapon::Info_Flags::Homing_aspect).set(Weapon::Info_Flags::Homing_heat).set(Weapon::Info_Flags::Homing_javelin);
8207 }
8208 }
8209 else if ( (esip != NULL) && (esip->flags[Ship::Info_Flags::Bomber]) )
8210 {
8211 wif_priority1.set(Weapon::Info_Flags::Bomber_plus);
8212 wif_priority2.set(Weapon::Info_Flags::Homing_aspect).set(Weapon::Info_Flags::Homing_heat).set(Weapon::Info_Flags::Homing_javelin);
8213 }
8214 else if (subsystem_strength > 100.0f)
8215 {
8216 wif_priority1.set(Weapon::Info_Flags::Puncture);
8217 wif_priority2.set(Weapon::Info_Flags::Homing_aspect).set(Weapon::Info_Flags::Homing_heat).set(Weapon::Info_Flags::Homing_javelin);
8218 }
8219 else if ((aip->ai_profile_flags[AI::Profile_Flags::Smart_secondary_weapon_selection]) && (en_objp->type == OBJ_ASTEROID)) //prefer dumbfires if its an asteroid
8220 {
8221 wif_priority1.reset();
8222 wif_priority2.reset();
8223 }
8224 else
8225 {
8226 wif_priority1.set(Weapon::Info_Flags::Homing_aspect).set(Weapon::Info_Flags::Homing_heat).set(Weapon::Info_Flags::Homing_javelin);
8227 wif_priority2.reset();
8228 }
8229
8230 ai_select_secondary_weapon(objp, swp, &wif_priority1, &wif_priority2);
8231 }
8232 }
8233
8234 /**
8235 * Return time, in seconds, at which this ship can next fire its current secondary weapon.
8236 */
set_secondary_fire_delay(ai_info * aip,ship * shipp,weapon_info * swip,bool burst)8237 float set_secondary_fire_delay(ai_info *aip, ship *shipp, weapon_info *swip, bool burst)
8238 {
8239 float t;
8240 if (burst) {
8241 t = swip->burst_delay;
8242 } else {
8243 t = swip->fire_wait; // Base delay for this weapon.
8244 }
8245 if (shipp->team == Player_ship->team) {
8246 t *= aip->ai_ship_fire_secondary_delay_scale_friendly;
8247 } else {
8248 t *= aip->ai_ship_fire_secondary_delay_scale_hostile;
8249 }
8250
8251 if (aip->ai_class_autoscale)
8252 t += (Num_ai_classes - aip->ai_class + 1) * 0.5f;
8253
8254 t *= frand_range(0.8f, 1.2f);
8255
8256 // For the missiles that fire fairly quickly, occasionally add an additional substantial delay.
8257 if (t < 5.0f)
8258 if (frand() < 0.5f)
8259 t = t * 2.0f + 2.0f;
8260
8261 return t;
8262 }
8263
8264
ai_chase_big_approach_set_goal(vec3d * goal_pos,object * attack_objp,object * target_objp,float * accel)8265 void ai_chase_big_approach_set_goal(vec3d *goal_pos, object *attack_objp, object *target_objp, float *accel)
8266 {
8267 float dist_to_goal;
8268
8269 // head straight toward him and maybe circle later
8270 vm_vec_avg(goal_pos, &attack_objp->pos, &target_objp->pos);
8271
8272 // get distance to goal
8273 dist_to_goal = vm_vec_dist(goal_pos, &attack_objp->pos);
8274
8275 // set accel
8276 if (dist_to_goal > 400.0f) {
8277 *accel = 1.0f;
8278 } else {
8279 *accel = dist_to_goal/400.0f;
8280 }
8281 }
8282
ai_chase_big_circle_set_goal(vec3d * goal_pos,object * attack_objp,object * target_objp,float * accel)8283 void ai_chase_big_circle_set_goal(vec3d *goal_pos, object *attack_objp, object *target_objp, float *accel)
8284 {
8285 get_tangent_point(goal_pos, attack_objp, &target_objp->pos, attack_objp->radius + target_objp->radius + 100.0f);
8286
8287 *accel = 1.0f;
8288 }
8289
8290 /**
8291 * Get the current and desired horizontal separations between target
8292 */
ai_chase_big_get_separations(object * attack_objp,object * target_objp,vec3d * horz_vec_to_target,float * desired_separation,float * cur_separation)8293 void ai_chase_big_get_separations(object *attack_objp, object *target_objp, vec3d *horz_vec_to_target, float *desired_separation, float *cur_separation)
8294 {
8295 float temp, r_target, r_attacker;
8296 float perp_dist;
8297 vec3d vec_to_target;
8298
8299 // get parameters of ships (as cylinders - radius and height)
8300 polymodel *pm = model_get(Ship_info[Ships[attack_objp->instance].ship_info_index].model_num);
8301
8302 // get radius of attacker (for rotations about forward)
8303 temp = MAX(pm->maxs.xyz.x, pm->maxs.xyz.y);
8304 r_attacker = MAX(-pm->mins.xyz.x, -pm->mins.xyz.y);
8305 r_attacker = MAX(temp, r_attacker);
8306
8307 // get radius of target (for rotations about forward)
8308 temp = MAX(pm->maxs.xyz.x, pm->maxs.xyz.y);
8309 r_target = MAX(-pm->mins.xyz.x, -pm->mins.xyz.y);
8310 r_target = MAX(temp, r_target);
8311
8312 // find separation between cylinders [if parallel]
8313 vm_vec_sub(&vec_to_target, &attack_objp->pos, &target_objp->pos);
8314
8315 // find the distance between centers along forward direction of ships
8316 perp_dist = vm_vec_dot(&vec_to_target, &target_objp->orient.vec.fvec);
8317
8318 // subtract off perp component to get "horizontal" separation vector between cylinders [ASSUMING parallel]
8319 vm_vec_scale_add(horz_vec_to_target, &vec_to_target, &target_objp->orient.vec.fvec, -perp_dist);
8320 *cur_separation = vm_vec_mag_quick(horz_vec_to_target);
8321
8322 // choose "optimal" separation of 1000 + r_target + r_attacker
8323 *desired_separation = 1000 + r_target + r_attacker;
8324 }
8325
ai_chase_big_parallel_set_goal(vec3d * goal_pos,object * attack_objp,object * target_objp,float * accel)8326 void ai_chase_big_parallel_set_goal(vec3d *goal_pos, object *attack_objp, object *target_objp, float *accel)
8327 {
8328 int opposing;
8329 float temp, r_target, r_attacker;
8330 float separation, optimal_separation;
8331 vec3d horz_vec_to_target;
8332
8333 // get parameters of ships (as cylinders - radius and height)
8334 polymodel *pm = model_get(Ship_info[Ships[attack_objp->instance].ship_info_index].model_num);
8335
8336 // get radius of attacker (for rotations about forward)
8337 temp = MAX(pm->maxs.xyz.x, pm->maxs.xyz.y);
8338 r_attacker = MAX(-pm->mins.xyz.x, -pm->mins.xyz.y);
8339 r_attacker = MAX(temp, r_attacker);
8340
8341 // get radius of target (for rotations about forward)
8342 temp = MAX(pm->maxs.xyz.x, pm->maxs.xyz.y);
8343 r_target = MAX(-pm->mins.xyz.x, -pm->mins.xyz.y);
8344 r_target = MAX(temp, r_target);
8345
8346 // are we opposing (only when other ship is not moving)
8347 opposing = ( vm_vec_dot(&attack_objp->orient.vec.fvec, &target_objp->orient.vec.fvec) < 0 );
8348
8349 ai_chase_big_get_separations(attack_objp, target_objp, &horz_vec_to_target, &optimal_separation, &separation);
8350
8351 // choose dist (2000) so that we don't bash
8352 float dist = 2000;
8353 if (opposing) {
8354 dist = - dist;
8355 }
8356
8357 // set the goal pos as dist forward from target along target forward
8358 vm_vec_scale_add(goal_pos, &target_objp->pos, &target_objp->orient.vec.fvec, dist);
8359 // then add horizontal separation
8360 vm_vec_scale_add2(goal_pos, &horz_vec_to_target, optimal_separation/separation);
8361
8362 // find the distance between centers along forward direction of ships
8363 vec3d vec_to_target;
8364 vm_vec_sub(&vec_to_target, &target_objp->pos, &attack_objp->pos);
8365 float perp_dist = vm_vec_dot(&vec_to_target, &target_objp->orient.vec.fvec);
8366
8367 float match_accel = 0.0f;
8368 float length_scale = attack_objp->radius;
8369
8370 if (Ship_info[Ships[attack_objp->instance].ship_info_index].max_vel.xyz.z != 0.0f) {
8371 match_accel = target_objp->phys_info.vel.xyz.z / Ship_info[Ships[attack_objp->instance].ship_info_index].max_vel.xyz.z;
8372 }
8373
8374 // if we're heading toward enemy ship, we want to keep going if we're ahead
8375 if (opposing) {
8376 perp_dist = -perp_dist;
8377 }
8378
8379 if (perp_dist > 0) {
8380 // falling behind, so speed up
8381 *accel = match_accel + (1.0f - match_accel) / length_scale * (perp_dist);
8382 } else {
8383 // up in front, so slow down
8384 *accel = match_accel - match_accel / length_scale * -perp_dist;
8385 *accel = MAX(0.0f, *accel);
8386 }
8387
8388 }
8389
8390 // Return *goal_pos for one cruiser to attack another (big ship).
8391 // Choose point fairly nearby that is not occupied by another cruiser.
ai_cruiser_chase_set_goal_pos(vec3d * goal_pos,object * pl_objp,object * en_objp)8392 void ai_cruiser_chase_set_goal_pos(vec3d *goal_pos, object *pl_objp, object *en_objp)
8393 {
8394 ai_info *aip;
8395
8396 aip = &Ai_info[Ships[pl_objp->instance].ai_index];
8397 float accel;
8398
8399 switch (aip->submode) {
8400 case SM_BIG_APPROACH:
8401 // do approach stuff;
8402 ai_chase_big_approach_set_goal(goal_pos, pl_objp, en_objp, &accel);
8403 break;
8404
8405 case SM_BIG_CIRCLE:
8406 // do circle stuff
8407 ai_chase_big_circle_set_goal(goal_pos, pl_objp, en_objp, &accel);
8408 break;
8409
8410 case SM_BIG_PARALLEL:
8411 // do parallel stuff
8412 ai_chase_big_parallel_set_goal(goal_pos, pl_objp, en_objp, &accel);
8413 break;
8414 }
8415 }
8416
maybe_hack_cruiser_chase_abort()8417 int maybe_hack_cruiser_chase_abort()
8418 {
8419 ship *shipp = &Ships[Pl_objp->instance];
8420 ship *eshipp = &Ships[En_objp->instance];
8421 ai_info *aip = &Ai_info[shipp->ai_index];
8422
8423 // mission sm3-08, sathanos chasing collosus
8424 if ( stricmp(Mission_filename, "sm3-08.fs2") == 0 ) {
8425 if (( stricmp(eshipp->ship_name, "colossus") == 0 ) || ( stricmp(shipp->ship_name, "colossus") == 0 )) {
8426 // Changed so all big ships attacking the Colossus will not do the chase code.
8427 // Did this so Beast wouldn't swerve away from Colossus. -- MK, 9/14/99
8428 ai_clear_ship_goals( aip );
8429 aip->mode = AIM_NONE;
8430 return 1;
8431 }
8432 }
8433
8434 return 0;
8435 }
8436
8437 // Make a big ship pursue another big ship.
8438 // (Note, called "ai_cruiser_chase" because we already have ai_chase_big() which means fighter chases big ship.
ai_cruiser_chase()8439 void ai_cruiser_chase()
8440 {
8441 ship *shipp = &Ships[Pl_objp->instance];
8442 ai_info *aip = &Ai_info[shipp->ai_index];
8443
8444 if (En_objp->type != OBJ_SHIP) {
8445 Int3();
8446 return;
8447 }
8448
8449 if (En_objp->instance < 0) {
8450 Int3();
8451 return;
8452 }
8453
8454 vec3d goal_pos;
8455
8456 // kamikaze - ram and explode
8457 if (aip->ai_flags[AI::AI_Flags::Kamikaze]) {
8458 ai_turn_towards_vector(&En_objp->pos, Pl_objp, nullptr, nullptr, 0.0f, 0);
8459 accelerate_ship(aip, 1.0f);
8460 }
8461
8462 // really track down and chase
8463 else {
8464 // check valid submode
8465 Assert( (aip->submode == SM_ATTACK) || (aip->submode == SM_BIG_APPROACH) || (aip->submode == SM_BIG_CIRCLE) || (aip->submode == SM_BIG_PARALLEL) );
8466
8467 // just entering, approach enemy ship
8468 if (aip->submode == SM_ATTACK) {
8469 aip->submode = SM_BIG_APPROACH;
8470 aip->submode_start_time = Missiontime;
8471 }
8472
8473 // desired accel
8474 float accel = 0.0f;
8475 vec3d *rvecp = NULL;
8476
8477 switch (aip->submode) {
8478 case SM_BIG_APPROACH:
8479 // do approach stuff;
8480 ai_chase_big_approach_set_goal(&goal_pos, Pl_objp, En_objp, &accel);
8481 // maybe set rvec
8482 break;
8483
8484 case SM_BIG_CIRCLE:
8485 // do circle stuff
8486 ai_chase_big_circle_set_goal(&goal_pos, Pl_objp, En_objp, &accel);
8487 // maybe set rvec
8488 break;
8489
8490 case SM_BIG_PARALLEL:
8491 // do parallel stuff
8492 ai_chase_big_parallel_set_goal(&goal_pos, Pl_objp, En_objp, &accel);
8493 //maybe set rvec
8494 break;
8495 }
8496
8497 // now move as desired
8498 ai_turn_towards_vector(&goal_pos, Pl_objp, nullptr, nullptr, 0.0f, 0, rvecp);
8499 accelerate_ship(aip, accel);
8500
8501 // maybe switch to new mode
8502 vec3d vec_to_enemy;
8503 float dist_to_enemy;
8504 int moving = (En_objp->phys_info.vel.xyz.z > 0.5f);
8505 vm_vec_sub(&vec_to_enemy, &En_objp->pos, &Pl_objp->pos);
8506 dist_to_enemy = vm_vec_mag_quick(&vec_to_enemy);
8507
8508 switch (aip->submode) {
8509 case SM_BIG_APPROACH:
8510 if ( dist_to_enemy < (Pl_objp->radius + En_objp->radius)*1.25f + 200.0f ) {
8511 // moving
8512 if (moving) {
8513 // if within 90 degrees of en forward, go into parallel, otherwise circle
8514 if ( vm_vec_dot(&En_objp->orient.vec.fvec, &Pl_objp->orient.vec.fvec) > 0 ) {
8515 aip->submode = SM_BIG_PARALLEL;
8516 aip->submode_start_time = Missiontime;
8517 }
8518 }
8519
8520 // otherwise cirle
8521 if ( !maybe_hack_cruiser_chase_abort() ) {
8522 aip->submode = SM_BIG_CIRCLE;
8523 aip->submode_start_time = Missiontime;
8524 }
8525 }
8526 break;
8527
8528 case SM_BIG_CIRCLE:
8529 // moving
8530 if (moving) {
8531 vec3d temp;
8532 float desired_sep, cur_sep;
8533 // we're behind the enemy ship
8534 if (vm_vec_dot(&vec_to_enemy, &En_objp->orient.vec.fvec) > 0) {
8535 // and we're turning toward the enemy
8536 if (vm_vec_dot(&En_objp->orient.vec.fvec, &Pl_objp->orient.vec.fvec) > 0) {
8537 // get separation
8538 ai_chase_big_get_separations(Pl_objp, En_objp, &temp, &desired_sep, &cur_sep);
8539 // and the separation is > 0.9 desired
8540 if (cur_sep > (0.9f * desired_sep)) {
8541 aip->submode = SM_BIG_PARALLEL;
8542 aip->submode_start_time = Missiontime;
8543 }
8544 }
8545 }
8546 } else {
8547 // still
8548 vec3d temp;
8549 float desired_sep, cur_sep;
8550 // we're behind the enemy ship
8551 if (vm_vec_dot(&vec_to_enemy, &En_objp->orient.vec.fvec) > 0) {
8552 // and we're turning toward the enemy
8553 if (vm_vec_dot(&En_objp->orient.vec.fvec, &Pl_objp->orient.vec.fvec) > 0) {
8554 // get separation
8555 ai_chase_big_get_separations(Pl_objp, En_objp, &temp, &desired_sep, &cur_sep);
8556 // and the separation is > 0.9 desired
8557 if (cur_sep > (0.9f * desired_sep)) {
8558 aip->submode = SM_BIG_PARALLEL;
8559 aip->submode_start_time = Missiontime;
8560 }
8561 }
8562 }
8563 // in front of ship
8564 else {
8565 // and we're turning toward the enemy
8566 if (vm_vec_dot(&En_objp->orient.vec.fvec, &Pl_objp->orient.vec.fvec) < 0) {
8567 // get separation
8568 ai_chase_big_get_separations(Pl_objp, En_objp, &temp, &desired_sep, &cur_sep);
8569 // and the separation is > 0.9 desired
8570 if (cur_sep > (0.9f * desired_sep)) {
8571 aip->submode = SM_BIG_PARALLEL;
8572 aip->submode_start_time = Missiontime;
8573 }
8574 }
8575 }
8576 }
8577 break;
8578
8579 case SM_BIG_PARALLEL:
8580 // we're opposing
8581 if ( vm_vec_dot(&Pl_objp->orient.vec.fvec, &En_objp->orient.vec.fvec) < 0 ) {
8582 // and the other ship is moving
8583 if (moving) {
8584 // and we no longer overlap
8585 if ( dist_to_enemy > (0.75 * (En_objp->radius + Pl_objp->radius)) ) {
8586 aip->submode = SM_BIG_APPROACH;
8587 aip->submode_start_time = Missiontime;
8588 }
8589 }
8590 }
8591 break;
8592 }
8593 }
8594 }
8595
8596 /**
8597 * Make object Pl_objp chase object En_objp
8598 */
ai_chase()8599 void ai_chase()
8600 {
8601 float dist_to_enemy;
8602 float dot_to_enemy, dot_from_enemy, real_dot_to_enemy;
8603 vec3d player_pos, enemy_pos, predicted_enemy_pos, real_vec_to_enemy, predicted_vec_to_enemy;
8604 ship *shipp = &Ships[Pl_objp->instance];
8605 ship_info *sip = &Ship_info[shipp->ship_info_index];
8606 ship_info *enemy_sip = NULL;
8607 ship_weapon *swp = &shipp->weapons;
8608 ai_info *aip = &Ai_info[shipp->ai_index];
8609 flagset<Ship::Info_Flags> enemy_sip_flags;
8610 flagset<Ship::Ship_Flags> enemy_shipp_flags;
8611 int has_fired = -1;
8612
8613 if (aip->mode != AIM_CHASE) {
8614 Int3();
8615 }
8616
8617 // by default we try to chase anything
8618 bool go_after_it = true;
8619
8620 if ( (sip->class_type > -1) && (En_objp->type == OBJ_SHIP) )
8621 {
8622 // default to not chasing for ships
8623 go_after_it = false;
8624 ship_info *esip = &Ship_info[Ships[En_objp->instance].ship_info_index];
8625 if (esip->class_type > -1)
8626 {
8627 ship_type_info *stp = &Ship_types[sip->class_type];
8628 size_t ap_size = stp->ai_actively_pursues.size();
8629 for(size_t i = 0; i < ap_size; i++)
8630 {
8631 if(stp->ai_actively_pursues[i] == esip->class_type) {
8632 go_after_it = true;
8633 break;
8634 }
8635 }
8636 }
8637 else
8638 {
8639 // if there was no class type then assume we can go after it ...
8640 go_after_it = true;
8641 // ... but also log this in debug so it doesn't go unchecked (NOTE that this can completely flood a debug log!)
8642 mprintf(("AI-WARNING: No class_type specified for '%s', assuming that it's ok to chase!\n", esip->name));
8643 }
8644 }
8645
8646 //WMC - Guess we do need this
8647 if (!go_after_it) {
8648 aip->mode = AIM_NONE;
8649 return;
8650 }
8651
8652 if (sip->class_type > -1 && (Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::AI_attempt_broadside])) {
8653 ai_cruiser_chase();
8654 return;
8655 }
8656
8657 Assert( En_objp != NULL );
8658
8659 if ( En_objp->type == OBJ_SHIP ) {
8660 enemy_sip_flags = Ship_info[Ships[En_objp->instance].ship_info_index].flags;
8661 enemy_sip = &Ship_info[Ships[En_objp->instance].ship_info_index];
8662 enemy_shipp_flags = Ships[En_objp->instance].flags;
8663 } else {
8664 enemy_sip_flags.reset();
8665 enemy_shipp_flags.reset();
8666 }
8667
8668 // If collided with target_objnum last frame, avoid that ship.
8669 // This should prevent the embarrassing behavior of ships getting stuck on each other
8670 // as if they were magnetically attracted. -- MK, 11/13/97.
8671 if ((aip->ai_flags[AI::AI_Flags::Target_collision]) || (aip->submode == SM_FLY_AWAY)) {
8672 ai_chase_fly_away(Pl_objp, aip);
8673 return;
8674 }
8675
8676 if ((The_mission.ai_profile->flags[AI::Profile_Flags::Better_collision_avoidance]) && sip->is_small_ship()) {
8677 // velocity / turn rate gives us a vector which represents the minimum 'bug out' distance
8678 // however this will be a significant underestimate, it doesn't take into account acceleration
8679 // with regards to its turn speed, nor already existing rotvel, nor the angular distance
8680 // to the particular avoidance vector it comes up with
8681 // These would significantly complicate the calculation, so for now, it is all bundled into this magic number
8682 const float collision_avoidance_aggression = 3.5f;
8683
8684 vec3d collide_vec = Pl_objp->phys_info.vel * (collision_avoidance_aggression / (PI2 / sip->srotation_time));
8685 float radius_contribution = (Pl_objp->phys_info.speed + Pl_objp->radius) / Pl_objp->phys_info.speed;
8686 collide_vec *= radius_contribution;
8687
8688 collide_vec += Pl_objp->pos;
8689 if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &collide_vec, 0.f, 0.1f))
8690 return;
8691 }
8692
8693 if (enemy_sip_flags.any_set()) {
8694 if (enemy_sip->is_big_or_huge()) {
8695 ai_big_chase();
8696 return;
8697 }
8698 }
8699
8700 ai_set_positions(Pl_objp, En_objp, aip, &player_pos, &enemy_pos);
8701 dist_to_enemy = vm_vec_dist_quick(&player_pos, &enemy_pos);
8702 vm_vec_sub(&real_vec_to_enemy, &enemy_pos, &player_pos);
8703
8704 //Enemy position for the purpose of aiming is already calculated differently, do it explicitly here
8705 ai_update_aim(aip);
8706
8707 vm_vec_normalize(&real_vec_to_enemy);
8708
8709 real_dot_to_enemy = vm_vec_dot(&real_vec_to_enemy, &Pl_objp->orient.vec.fvec);
8710
8711 int is_stealthy_ship = 0;
8712 if ( (enemy_sip_flags.any_set() ) && (enemy_shipp_flags[Ship::Ship_Flags::Stealth]) ) {
8713 if ( ai_is_stealth_visible(Pl_objp, En_objp) != STEALTH_FULLY_TARGETABLE ) {
8714 is_stealthy_ship = 1;
8715 }
8716 }
8717
8718 // Can only acquire lock on a target that isn't hidden from sensors
8719 if ( En_objp->type == OBJ_SHIP && !(Ships[En_objp->instance].flags[Ship::Ship_Flags::Hidden_from_sensors]) && !is_stealthy_ship ) {
8720 update_aspect_lock_information(aip, &real_vec_to_enemy, dist_to_enemy, En_objp->radius);
8721 } else {
8722 aip->current_target_is_locked = 0;
8723 aip->ai_flags.remove(AI::AI_Flags::Seek_lock);
8724 }
8725
8726 // If seeking lock, try to point directly at ship, else predict position so lasers can hit it.
8727 // If just acquired target, or target is not in reasonable cone, don't refine believed enemy position.
8728 if ((real_dot_to_enemy < 0.25f) || (aip->target_time < 1.0f)) {
8729 predicted_enemy_pos = enemy_pos;
8730 } else if (aip->ai_flags[AI::AI_Flags::Seek_lock]) {
8731 if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ramming_stationary_targets_bug]) {
8732 // fixed by Mantis 3147 - Bash orientation if aspect seekers are equipped.
8733 set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &aip->last_aim_enemy_pos, &aip->last_aim_enemy_vel, aip); // Set G_fire_pos
8734 predicted_enemy_pos = enemy_pos;
8735 G_predicted_pos = predicted_enemy_pos;
8736 } else {
8737 // retail behavior
8738 predicted_enemy_pos = enemy_pos;
8739 }
8740 } else {
8741 // Set predicted_enemy_pos.
8742 // See if attacking a subsystem.
8743 if (aip->targeted_subsys != NULL) {
8744 Assert(En_objp->type == OBJ_SHIP);
8745 if (get_shield_pct(En_objp) < HULL_DAMAGE_THRESHOLD_PERCENT) {
8746 if (aip->targeted_subsys != NULL) {
8747 get_subsystem_pos(&enemy_pos, En_objp, aip->targeted_subsys);
8748 predicted_enemy_pos = enemy_pos;
8749 predicted_vec_to_enemy = real_vec_to_enemy;
8750 } else {
8751 set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &aip->last_aim_enemy_pos, &aip->last_aim_enemy_vel, aip);
8752 set_target_objnum(aip, -1);
8753 }
8754
8755 } else {
8756 set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &aip->last_aim_enemy_pos, &aip->last_aim_enemy_vel, aip);
8757 }
8758 } else {
8759 set_predicted_enemy_pos(&predicted_enemy_pos, Pl_objp, &aip->last_aim_enemy_pos, &aip->last_aim_enemy_vel, aip);
8760 }
8761 }
8762
8763 vm_vec_sub(&predicted_vec_to_enemy, &predicted_enemy_pos, &player_pos);
8764
8765 vm_vec_normalize(&predicted_vec_to_enemy);
8766
8767 dot_to_enemy = vm_vec_dot(&Pl_objp->orient.vec.fvec, &predicted_vec_to_enemy);
8768 dot_from_enemy= - vm_vec_dot(&En_objp->orient.vec.fvec, &real_vec_to_enemy);
8769
8770 // Set turn and acceleration based on submode.
8771 switch (aip->submode) {
8772 case SM_CONTINUOUS_TURN:
8773 ai_chase_ct();
8774 break;
8775
8776 case SM_STEALTH_FIND:
8777 ai_stealth_find();
8778 break;
8779
8780 case SM_STEALTH_SWEEP:
8781 ai_stealth_sweep();
8782 break;
8783
8784 case SM_ATTACK:
8785 case SM_SUPER_ATTACK:
8786 case SM_ATTACK_FOREVER:
8787 case AIS_CHASE_GLIDEATTACK:
8788 case AIS_CHASE_CIRCLESTRAFE:
8789 if (vm_vec_dist_quick(&Pl_objp->pos, &predicted_enemy_pos) > 100.0f + En_objp->radius * 2.0f) {
8790 if (maybe_avoid_big_ship(Pl_objp, En_objp, aip, &predicted_enemy_pos, 10.0f))
8791 return;
8792 }
8793
8794 ai_chase_attack(aip, sip, &predicted_enemy_pos, dist_to_enemy, sip->model_num);
8795 break;
8796
8797 case SM_EVADE_SQUIGGLE:
8798 ai_chase_es(aip);
8799 break;
8800
8801 case SM_EVADE_BRAKE:
8802 ai_chase_eb(aip, &predicted_enemy_pos);
8803 break;
8804
8805 case SM_EVADE:
8806 evade_ship();
8807 break;
8808
8809 case SM_AVOID:
8810 avoid_ship();
8811 break;
8812
8813 case SM_GET_BEHIND:
8814 get_behind_ship(aip);
8815 break;
8816
8817 case SM_GET_AWAY: // Used to get away from opponent to prevent endless circling.
8818 ai_chase_ga(aip, sip);
8819 break;
8820
8821 case SM_EVADE_WEAPON:
8822 evade_weapon();
8823 break;
8824
8825 default:
8826 aip->last_attack_time = Missiontime;
8827 aip->submode = SM_ATTACK;
8828 aip->submode_start_time = Missiontime;
8829 }
8830
8831 //Maybe apply random sidethrust, depending on the current submode
8832 //The following are valid targets for random sidethrust (circle strafe uses it too, but that is handled separately)
8833 if (aip->submode == SM_ATTACK ||
8834 aip->submode == SM_SUPER_ATTACK ||
8835 aip->submode == SM_EVADE_SQUIGGLE ||
8836 aip->submode == SM_EVADE ||
8837 aip->submode == SM_GET_AWAY ||
8838 aip->submode == AIS_CHASE_GLIDEATTACK)
8839 {
8840 //Re-roll for random sidethrust every 2 seconds
8841 //Also, only do this if we've recently been hit or are in current primary weapon range of target
8842 float current_weapon_range = Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].weapon_range;
8843 if (static_randf((Missiontime + static_rand(aip->shipnum)) >> 17) < aip->ai_random_sidethrust_percent &&
8844 ((Missiontime - aip->last_hit_time < i2f(5) && aip->hitter_objnum >= 0) || dist_to_enemy < current_weapon_range))
8845 {
8846 do_random_sidethrust(aip, sip);
8847 }
8848 }
8849
8850 // Maybe choose a new submode.
8851 if ( (aip->submode != SM_AVOID) && (aip->submode != SM_ATTACK_FOREVER) ) {
8852 // If a very long time since attacked, attack no matter what!
8853 if (Missiontime - aip->last_attack_time > i2f(6)) {
8854 if ( (aip->submode != SM_SUPER_ATTACK) && (aip->submode != SM_GET_AWAY) && !(aip->ai_flags[AI::AI_Flags::Stealth_pursuit]) ) {
8855 aip->submode = SM_SUPER_ATTACK;
8856 aip->submode_start_time = Missiontime;
8857 aip->last_attack_time = Missiontime;
8858 }
8859 }
8860
8861 //SUSHI: Alternate stalemate dection method: if nobody has hit each other for a while
8862 //(and we've been near that whole time), shake things up somehow
8863 //Only do this if stalemate time threshold > 0
8864 if (aip->ai_stalemate_time_thresh > 0.0f &&
8865 Missiontime - aip->last_hit_target_time > fl2f(aip->ai_stalemate_time_thresh) &&
8866 Missiontime - aip->last_hit_time > fl2f(aip->ai_stalemate_time_thresh) &&
8867 aip->time_enemy_near > aip->ai_stalemate_time_thresh &&
8868 (dot_to_enemy < 0.95f - 0.5f * En_objp->radius/MAX(1.0f, En_objp->radius + dist_to_enemy)))
8869 {
8870 //Every second, evaluate whether or not to break stalemate. The more patient the ship, the less likely this is.
8871 if (static_randf((Missiontime + static_rand(aip->shipnum)) >> 16) > (aip->ai_patience * .01))
8872 {
8873 if ((sip->can_glide == true) && (frand() < aip->ai_glide_attack_percent)) {
8874 //Maybe use glide attack
8875 aip->submode = AIS_CHASE_GLIDEATTACK;
8876 aip->submode_start_time = Missiontime;
8877 aip->last_hit_target_time = Missiontime;
8878 aip->time_enemy_near = 0.0f;
8879 } else {
8880 //Otherwise, try to get away
8881 aip->submode = SM_GET_AWAY;
8882 aip->submode_start_time = Missiontime;
8883 aip->last_hit_target_time = Missiontime;
8884 aip->time_enemy_near = 0.0f;
8885 }
8886 }
8887 }
8888
8889 // If a collision is expected, pull out!
8890 // If enemy is pointing away and moving a bit, don't worry about collision detection.
8891 if ((dot_from_enemy > 0.5f) || (En_objp->phys_info.speed < 10.0f)) {
8892 //If we're in circle strafe mode, don't worry about colliding with the target
8893 if (aip->submode != AIS_CHASE_CIRCLESTRAFE && might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, 4.0f)) {
8894 if ((Missiontime - aip->last_hit_time > F1_0*4) && (dist_to_enemy < Pl_objp->radius*2 + En_objp->radius*2)) {
8895 accelerate_ship(aip, -1.0f);
8896 } else {
8897 aip->submode = SM_AVOID;
8898 aip->submode_start_time = Missiontime;
8899 }
8900 }
8901 }
8902 }
8903
8904 switch (aip->submode) {
8905 case SM_CONTINUOUS_TURN:
8906 if (Missiontime - aip->submode_start_time > i2f(3)) {
8907 aip->last_attack_time = Missiontime;
8908 aip->submode = SM_ATTACK;
8909 aip->submode_start_time = Missiontime;
8910 }
8911 break;
8912
8913 case SM_ATTACK:
8914 // if target is stealth and stealth not visible, then enter stealth find mode
8915 if ( (aip->ai_flags[AI::AI_Flags::Stealth_pursuit]) && (ai_is_stealth_visible(Pl_objp, En_objp) == STEALTH_NOT_IN_FRUSTUM) ) {
8916 aip->submode = SM_STEALTH_FIND;
8917 aip->submode_start_time = Missiontime;
8918 aip->submode_parm0 = SM_SF_AHEAD;
8919 } else if (dist_to_enemy < CIRCLE_STRAFE_MAX_DIST + En_objp->radius &&
8920 (En_objp->phys_info.speed < MAX(sip->max_vel.xyz.x, sip->max_vel.xyz.y) * 1.5f) &&
8921 (static_randf((Missiontime + static_rand(aip->shipnum)) >> 19) < aip->ai_circle_strafe_percent)) {
8922 aip->submode = AIS_CHASE_CIRCLESTRAFE;
8923 aip->submode_start_time = Missiontime;
8924 aip->last_attack_time = Missiontime;
8925 } else if (ai_near_full_strength(Pl_objp) && (Missiontime - aip->last_hit_target_time > i2f(3)) && (dist_to_enemy < 500.0f) && (dot_to_enemy < 0.5f)) {
8926 aip->submode = SM_SUPER_ATTACK;
8927 aip->submode_start_time = Missiontime;
8928 aip->last_attack_time = Missiontime;
8929 } else if ((Missiontime - aip->last_hit_target_time > i2f(6)) &&
8930 (dist_to_enemy < 500.0f) && (dot_to_enemy < 0.2f) &&
8931 (frand() < (float) Game_skill_level/NUM_SKILL_LEVELS)) {
8932 aip->submode = SM_GET_AWAY;
8933 aip->submode_start_time = Missiontime;
8934 aip->last_hit_target_time = Missiontime;
8935 } else if (enemy_sip != NULL && (enemy_sip->is_small_ship())
8936 && (dot_to_enemy < dot_from_enemy)
8937 && (En_objp->phys_info.speed > 15.0f)
8938 && (dist_to_enemy < 200.0f)
8939 && (dist_to_enemy > 50.0f)
8940 && (dot_to_enemy < 0.1f)
8941 && (Missiontime - aip->submode_start_time > i2f(2))) {
8942 aip->submode = SM_EVADE_BRAKE;
8943 aip->submode_start_time = Missiontime;
8944 } else if ((dot_to_enemy > 0.2f) && (dot_from_enemy > -0.2f) && (dot_from_enemy < 0.1f)) {
8945 aip->submode = SM_GET_BEHIND;
8946 aip->submode_start_time = Missiontime;
8947 } else if ( enemy_sip != NULL && (enemy_sip->is_small_ship()) && (dist_to_enemy < 150.0f) && (dot_from_enemy > dot_to_enemy + 0.5f + aip->ai_courage*.002)) {
8948 float get_away_chance = (aip->ai_get_away_chance == FLT_MIN)
8949 ? (float)(aip->ai_class + Game_skill_level)/(Num_ai_classes + NUM_SKILL_LEVELS)
8950 : aip->ai_get_away_chance;
8951 if ((Missiontime - aip->last_hit_target_time > i2f(5)) && (frand() < get_away_chance)) {
8952 aip->submode = SM_GET_AWAY;
8953 aip->submode_start_time = Missiontime;
8954 aip->last_hit_target_time = Missiontime;
8955 } else {
8956 aip->submode = SM_EVADE_SQUIGGLE;
8957 aip->submode_start_time = Missiontime;
8958 }
8959 } else if ( enemy_sip != NULL && (enemy_sip->is_small_ship()) && (Missiontime - aip->submode_start_time > F1_0 * 2)) {
8960 if ((dot_to_enemy < 0.8f) && (dot_from_enemy > dot_to_enemy)) {
8961 if (frand() > 0.5f) {
8962 aip->submode = SM_CONTINUOUS_TURN;
8963 aip->submode_start_time = Missiontime;
8964 aip->submode_parm0 = Random::next() & 0x0f;
8965 } else {
8966 aip->submode = SM_EVADE;
8967 aip->submode_start_time = Missiontime;
8968 }
8969 } else {
8970 aip->submode_start_time = Missiontime;
8971 }
8972 }
8973
8974 aip->last_attack_time = Missiontime;
8975
8976 break;
8977
8978 case SM_EVADE_SQUIGGLE:
8979 if ((Missiontime - aip->submode_start_time > i2f(5)) || (dist_to_enemy > 300.0f)) {
8980 if ((dist_to_enemy < 100.0f) && (dot_to_enemy < 0.0f) && (dot_from_enemy > 0.5f)) {
8981 aip->submode = SM_EVADE_BRAKE;
8982 } else if ((Pl_objp->phys_info.speed >= Pl_objp->phys_info.max_vel.xyz.z / 2.0f) && (sip->can_glide == true) && (frand() < aip->ai_glide_attack_percent)) {
8983 aip->last_attack_time = Missiontime;
8984 aip->submode = AIS_CHASE_GLIDEATTACK;
8985 } else {
8986 aip->last_attack_time = Missiontime;
8987 aip->submode = SM_ATTACK;
8988 }
8989 aip->submode_start_time = Missiontime;
8990 }
8991 break;
8992
8993 case SM_EVADE_BRAKE:
8994 if ((dist_to_enemy < 15.0f) || (En_objp->phys_info.speed < 10.0f)) {
8995 aip->submode = SM_AVOID;
8996 aip->submode_start_time = Missiontime;
8997 } else if ((dot_to_enemy > 0.9f) || ((dot_from_enemy > 0.9f) && (Missiontime - aip->submode_start_time > i2f(1)))) {
8998 aip->last_attack_time = Missiontime;
8999 aip->submode = SM_ATTACK;
9000 aip->submode_start_time = Missiontime;
9001 } else if (Missiontime - aip->submode_start_time > i2f(4)) {
9002 aip->last_attack_time = Missiontime;
9003 aip->submode = SM_ATTACK;
9004 aip->submode_start_time = Missiontime;
9005 }
9006 break;
9007
9008 case SM_EVADE:
9009 // Modified by MK on 5/5/97 to keep trying to regain attack mode. It's what a human would do.
9010 if ((dot_to_enemy < 0.2f) && (dot_from_enemy < 0.8f) && (dist_to_enemy < 100.0f) && (En_objp->phys_info.speed > 15.0f)) {
9011 aip->last_attack_time = Missiontime;
9012 aip->submode = SM_EVADE_BRAKE;
9013 aip->submode_start_time = Missiontime;
9014 } else if (((dot_to_enemy > dot_from_enemy - 0.1f)
9015 && (Missiontime > aip->submode_start_time + i2f(1)))
9016 || (dist_to_enemy > 150.0f + 2*(Pl_objp->radius + En_objp->radius))) {
9017 aip->last_attack_time = Missiontime;
9018 aip->submode = SM_ATTACK;
9019 aip->submode_start_time = Missiontime;
9020 } else if (Missiontime - aip->submode_start_time > i2f(2))
9021 if (dot_from_enemy > 0.8f) {
9022 aip->submode = SM_EVADE_SQUIGGLE;
9023 aip->submode_start_time = Missiontime;
9024 }
9025
9026 break;
9027
9028 case SM_SUPER_ATTACK:
9029 // if stealth and invisible, enter stealth find mode
9030 if ( (aip->ai_flags[AI::AI_Flags::Stealth_pursuit]) && (ai_is_stealth_visible(Pl_objp, En_objp) == STEALTH_NOT_IN_FRUSTUM) ) {
9031 aip->submode = SM_STEALTH_FIND;
9032 aip->submode_start_time = Missiontime;
9033 aip->submode_parm0 = SM_SF_AHEAD;
9034 } else if (dist_to_enemy < CIRCLE_STRAFE_MAX_DIST + En_objp->radius &&
9035 (En_objp->phys_info.speed < MAX(sip->max_vel.xyz.x, sip->max_vel.xyz.y) * 1.5f) &&
9036 (static_randf((Missiontime + static_rand(aip->shipnum)) >> 19) < aip->ai_circle_strafe_percent)) {
9037 aip->submode = AIS_CHASE_CIRCLESTRAFE;
9038 aip->submode_start_time = Missiontime;
9039 aip->last_attack_time = Missiontime;
9040 } else if ((dist_to_enemy < 100.0f) && (dot_to_enemy < 0.8f) && (enemy_sip != NULL) && (enemy_sip->is_small_ship()) && (Missiontime - aip->submode_start_time > i2f(5) )) {
9041 aip->ai_flags.remove(AI::AI_Flags::Attack_slowly); // Just in case, clear here.
9042
9043 float get_away_chance = (aip->ai_get_away_chance == FLT_MIN)
9044 ? (float)(aip->ai_class + Game_skill_level)/(Num_ai_classes + NUM_SKILL_LEVELS)
9045 : aip->ai_get_away_chance;
9046
9047 switch (Random::next(5)) {
9048 case 0:
9049 aip->submode = SM_CONTINUOUS_TURN;
9050 aip->submode_start_time = Missiontime;
9051 break;
9052 case 1:
9053 aip->submode_start_time = Missiontime; // Stay in super attack mode
9054 break;
9055 case 2:
9056 case 3:
9057 if (frand() < (float) 0.5f * get_away_chance) {
9058 aip->submode = SM_GET_AWAY;
9059 aip->submode_start_time = Missiontime;
9060 } else {
9061 aip->submode = SM_EVADE;
9062 aip->submode_start_time = Missiontime;
9063 }
9064 break;
9065 case 4:
9066 if (dot_from_enemy + (NUM_SKILL_LEVELS - Game_skill_level) * 0.1f > dot_to_enemy) { // Less likely to GET_AWAY at lower skill levels.
9067 aip->submode = SM_EVADE;
9068 aip->submode_start_time = Missiontime;
9069 } else {
9070 aip->submode = SM_GET_AWAY;
9071 aip->submode_start_time = Missiontime;
9072 }
9073 break;
9074 default:
9075 Int3(); // Impossible!
9076 }
9077 }
9078
9079 aip->last_attack_time = Missiontime;
9080
9081 break;
9082
9083 case SM_AVOID:
9084 if ((dot_to_enemy > -0.2f) && (dist_to_enemy / (dot_to_enemy + 0.3f) < 100.0f)) {
9085 aip->submode_start_time = Missiontime;
9086 } else if (Missiontime - aip->submode_start_time > i2f(1)/2) {
9087 if (might_collide_with_ship(Pl_objp, En_objp, dot_to_enemy, dist_to_enemy, 3.0f)) {
9088 aip->submode_start_time = Missiontime;
9089 } else {
9090 aip->submode = SM_GET_BEHIND;
9091 aip->submode_start_time = Missiontime;
9092 }
9093 }
9094
9095 break;
9096
9097 case SM_GET_BEHIND:
9098 if ((dot_from_enemy < -0.7f) || (Missiontime - aip->submode_start_time > i2f(2))) {
9099 aip->submode = SM_ATTACK;
9100 aip->submode_start_time = Missiontime;
9101 aip->last_attack_time = Missiontime;
9102 }
9103 break;
9104
9105 case SM_GET_AWAY:
9106 if (Missiontime - aip->submode_start_time > i2f(2)) {
9107 float rand_dist;
9108
9109 rand_dist = ((Missiontime >> 17) & 0x03) * 100.0f + 200.0f; // Some value in 200..500
9110 if ((Missiontime - aip->submode_start_time > i2f(5)) || (dist_to_enemy > rand_dist) || (dot_from_enemy < 0.4f)) {
9111 //Sometimes use a glide attack instead (if we can)
9112 if ((sip->can_glide == true) && (frand() < aip->ai_glide_attack_percent)) {
9113 aip->submode = AIS_CHASE_GLIDEATTACK;
9114 }
9115 else {
9116 aip->ai_flags.set(AI::AI_Flags::Attack_slowly);
9117 aip->submode = SM_ATTACK;
9118 }
9119
9120 aip->submode_start_time = Missiontime;
9121 aip->time_enemy_in_range = 2.0f; // Cheat. Presumably if they were running away from you, they were monitoring you!
9122 aip->last_attack_time = Missiontime;
9123 }
9124 }
9125 break;
9126
9127 case SM_EVADE_WEAPON:
9128 if (aip->danger_weapon_objnum == -1) {
9129 aip->submode = SM_ATTACK;
9130 aip->submode_start_time = Missiontime;
9131 aip->last_attack_time = Missiontime;
9132 }
9133 break;
9134
9135 // Either change to SM_ATTACK or AIM_FIND_STEALTH
9136 case SM_STEALTH_FIND:
9137 // if time > 5 sec change mode to sweep
9138 if ( !(aip->ai_flags[AI::AI_Flags::Stealth_pursuit]) || (ai_is_stealth_visible(Pl_objp, En_objp) == STEALTH_IN_FRUSTUM) ) {
9139 aip->submode = SM_ATTACK;
9140 aip->submode_start_time = Missiontime;
9141 aip->last_attack_time = Missiontime;
9142 // sweep if I can't find in 5 sec or bail from find
9143 } else if ( ((Missiontime - aip->submode_start_time) > i2f(5)) || (aip->submode_parm0 == SM_SF_BAIL) ) {
9144 // begin sweep mode
9145 aip->submode = SM_STEALTH_SWEEP;
9146 aip->submode_start_time = Missiontime;
9147 aip->last_attack_time = Missiontime;
9148 aip->submode_parm0 = SM_SS_SET_GOAL;
9149 }
9150 break;
9151
9152 case SM_STEALTH_SWEEP:
9153 if ( !(aip->ai_flags[AI::AI_Flags::Stealth_pursuit]) || (ai_is_stealth_visible(Pl_objp, En_objp) == STEALTH_IN_FRUSTUM) ) {
9154 aip->submode = SM_ATTACK;
9155 aip->submode_start_time = Missiontime;
9156 aip->last_attack_time = Missiontime;
9157 } else if ( (timestamp() - aip->stealth_last_visible_stamp) < 5000 ) {
9158 // go back to find mode
9159 aip->submode = SM_STEALTH_FIND;
9160 aip->submode_start_time = Missiontime;
9161 aip->submode_parm0 = SM_SF_AHEAD;
9162 } else if ( aip->submode_parm0 == SM_SS_DONE ) {
9163 // set target objnum = -1
9164 set_target_objnum(aip, -1);
9165
9166 // set submode to attack
9167 aip->submode = SM_ATTACK;
9168 aip->submode_start_time = Missiontime;
9169 aip->last_attack_time = Missiontime;
9170 }
9171 break;
9172
9173 case SM_ATTACK_FOREVER: // Engines blown, just attack.
9174 break;
9175
9176 case AIS_CHASE_GLIDEATTACK:
9177 //Glide attack lasts at least as long as it takes to turn around and fire for a couple seconds.
9178 if (Missiontime - aip->submode_start_time > i2f((int)(sip->rotation_time.xyz.x) + 4 + static_rand_range(Missiontime >> 19, 0, 3))) {
9179 aip->submode = SM_ATTACK;
9180 aip->submode_start_time = Missiontime;
9181 aip->last_attack_time = Missiontime;
9182 }
9183 aip->last_attack_time = Missiontime;
9184 break;
9185
9186 case AIS_CHASE_CIRCLESTRAFE:
9187 //Break out of circle strafe if the target is too far, moving too fast, or we've been doing this for a while
9188 if ((dist_to_enemy > CIRCLE_STRAFE_MAX_DIST + En_objp->radius) ||
9189 (Missiontime - aip->submode_start_time > i2f(8)) ||
9190 (En_objp->phys_info.speed > MAX(sip->max_vel.xyz.x, sip->max_vel.xyz.y) * 1.5)) {
9191 aip->submode = SM_ATTACK;
9192 aip->submode_start_time = Missiontime;
9193 aip->last_attack_time = Missiontime;
9194 }
9195 aip->last_attack_time = Missiontime;
9196 break;
9197
9198 default:
9199 aip->submode = SM_ATTACK;
9200 aip->submode_start_time = Missiontime;
9201 aip->last_attack_time = Missiontime;
9202 }
9203
9204
9205 //Update time enemy near
9206 //Ignore for stealth ships that can't be seen
9207 //If we're trying to get away, recent counter
9208 //Only do this if stalemate distance threshold > 0
9209 if (aip->ai_stalemate_dist_thresh > 0.0f &&
9210 dist_to_enemy < aip->ai_stalemate_dist_thresh &&
9211 aip->submode != SM_GET_AWAY && aip->submode != AIS_CHASE_GLIDEATTACK && aip->submode != SM_FLY_AWAY &&
9212 (!(aip->ai_flags[AI::AI_Flags::Stealth_pursuit]) || (ai_is_stealth_visible(Pl_objp, En_objp) == STEALTH_IN_FRUSTUM)))
9213 {
9214 aip->time_enemy_near += flFrametime;
9215 }
9216 else
9217 {
9218 aip->time_enemy_near *= (1.0f - flFrametime);
9219 if (aip->time_enemy_near < 0.0f)
9220 aip->time_enemy_near = 0.0f;
9221 }
9222
9223 // Maybe fire primary weapon and update time_enemy_in_range
9224 if (aip->mode != AIM_EVADE) {
9225 if (dot_to_enemy > 0.95f - 0.5f * En_objp->radius/MAX(1.0f, En_objp->radius + dist_to_enemy)) {
9226 aip->time_enemy_in_range += flFrametime;
9227
9228 // Chance of hitting ship is based on dot product of firing ship's forward vector with vector to ship
9229 // and also the size of the target relative to distance to target.
9230 if (dot_to_enemy > MAX(0.5f, 0.90f + aip->ai_accuracy/10.0f - En_objp->radius/MAX(1.0f,dist_to_enemy))) {
9231
9232 ship *temp_shipp;
9233 ship_weapon *tswp;
9234
9235 temp_shipp = &Ships[Pl_objp->instance];
9236 tswp = &temp_shipp->weapons;
9237 if ( tswp->num_primary_banks > 0 ) {
9238 float scale;
9239 Assert(tswp->current_primary_bank < tswp->num_primary_banks);
9240 weapon_info *pwip = &Weapon_info[tswp->primary_bank_weapons[tswp->current_primary_bank]];
9241
9242 // Less likely to fire if far away and moving.
9243 scale = pwip->max_speed/(En_objp->phys_info.speed + pwip->max_speed);
9244 if (scale > 0.6f)
9245 scale = (scale - 0.6f) * 1.5f;
9246 else
9247 scale = 0.0f;
9248 float range_min = 0.0f;
9249 float range_max = pwip->max_speed * (1.0f + scale);
9250 if (aip->ai_profile_flags[AI::Profile_Flags::Use_actual_primary_range]) {
9251 range_max = std::min({range_max, pwip->max_speed * pwip->lifetime, pwip->weapon_range});
9252 range_min = pwip->weapon_min_range;
9253 }
9254 if ((dist_to_enemy < range_max) && (dist_to_enemy >= range_min)) {
9255 if(ai_fire_primary_weapon(Pl_objp) == 1){
9256 has_fired = 1;
9257 }else{
9258 has_fired = -1;
9259 }
9260 }
9261 }
9262
9263 if ( tswp->num_secondary_banks > 0) {
9264
9265 // Don't fire secondaries at a protected ship.
9266 if (!(En_objp->flags[Object::Object_Flags::Protected])) {
9267 ai_choose_secondary_weapon(Pl_objp, aip, En_objp);
9268 int current_bank = tswp->current_secondary_bank;
9269
9270 if (current_bank > -1) {
9271 weapon_info *swip = &Weapon_info[tswp->secondary_bank_weapons[current_bank]];
9272 if (aip->ai_flags[AI::AI_Flags::Unload_secondaries]) {
9273 if (timestamp_until(swp->next_secondary_fire_stamp[current_bank]) > swip->fire_wait*1000.0f) {
9274 swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (swip->fire_wait*1000.0f));
9275 }
9276 }
9277
9278 if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank]) && weapon_target_satisfies_lock_restrictions(swip, En_objp)) {
9279 if (current_bank >= 0) {
9280 float firing_range;
9281
9282 if (swip->wi_flags[Weapon::Info_Flags::Local_ssm])
9283 firing_range=swip->lssm_lock_range; //that should be enough
9284 else if (swip->wi_flags[Weapon::Info_Flags::Bomb])
9285 firing_range = MIN((swip->max_speed * swip->lifetime * 0.75f), swip->weapon_range);
9286 else
9287 {
9288 float secondary_range_mult = (aip->ai_secondary_range_mult == FLT_MIN)
9289 ? (float)(Game_skill_level + 1 + (3 * aip->ai_class/(Num_ai_classes - 1)))/NUM_SKILL_LEVELS
9290 : aip->ai_secondary_range_mult;
9291
9292 firing_range = MIN((swip->max_speed * swip->lifetime * secondary_range_mult), swip->weapon_range);
9293 }
9294
9295
9296 // reduce firing range in nebula
9297 extern int Nebula_sec_range;
9298 if ((The_mission.flags[Mission::Mission_Flags::Fullneb]) && Nebula_sec_range) {
9299 firing_range *= 0.8f;
9300 }
9301
9302 // If firing a spawn weapon, distance doesn't matter.
9303 int spawn_fire = 0;
9304
9305 if (swip->wi_flags[Weapon::Info_Flags::Spawn]) {
9306 int count;
9307
9308 count = num_nearby_fighters(iff_get_attackee_mask(obj_team(Pl_objp)), &Pl_objp->pos, 1000.0f);
9309
9310 if (count > 3)
9311 spawn_fire = 1;
9312 else if (count >= 1) {
9313 float hull_percent = Pl_objp->hull_strength/temp_shipp->ship_max_hull_strength;
9314
9315 if (hull_percent < 0.01f)
9316 hull_percent = 0.01f;
9317
9318 if (frand() < 0.25f/(30.0f*hull_percent) * count) // With timestamp below, this means could fire in 30 seconds if one enemy.
9319 spawn_fire = 1;
9320 }
9321 }
9322
9323 if (spawn_fire || (dist_to_enemy < firing_range)) {
9324 if (ai_fire_secondary_weapon(Pl_objp)) {
9325 // Only if weapon was fired do we specify time until next fire. If not fired, done in ai_fire_secondary...
9326 float t;
9327 int current_bank_adjusted = MAX_SHIP_PRIMARY_BANKS + current_bank;
9328
9329 if ((aip->ai_flags[AI::AI_Flags::Unload_secondaries]) || (swip->burst_flags[Weapon::Burst_Flags::Fast_firing])) {
9330 if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
9331 t = swip->burst_delay;
9332 swp->burst_counter[current_bank_adjusted]++;
9333 } else {
9334 t = swip->fire_wait;
9335 if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
9336 swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
9337 } else {
9338 swp->burst_counter[current_bank_adjusted] = 0;
9339 }
9340 swp->burst_seed[current_bank_adjusted] = Random::next();
9341 }
9342 } else {
9343 if (swip->burst_shots > swp->burst_counter[current_bank_adjusted]) {
9344 t = set_secondary_fire_delay(aip, temp_shipp, swip, true);
9345 swp->burst_counter[current_bank_adjusted]++;
9346 } else {
9347 t = set_secondary_fire_delay(aip, temp_shipp, swip, false);
9348 if ((swip->burst_shots > 0) && (swip->burst_flags[Weapon::Burst_Flags::Random_length])) {
9349 swp->burst_counter[current_bank_adjusted] = Random::next(swip->burst_shots);
9350 } else {
9351 swp->burst_counter[current_bank_adjusted] = 0;
9352 }
9353 swp->burst_seed[current_bank_adjusted] = Random::next();
9354 }
9355 }
9356 swp->next_secondary_fire_stamp[current_bank] = timestamp((int) (t*1000.0f));
9357 }
9358 } else {
9359 swp->next_secondary_fire_stamp[current_bank] = timestamp(250);
9360 }
9361 }
9362 }
9363 }
9364 }
9365 }
9366 }
9367 } else {
9368 if (flFrametime < 1.0f)
9369 aip->time_enemy_in_range *= (1.0f - flFrametime);
9370 else
9371 aip->time_enemy_in_range = 0;
9372 }
9373 } else {
9374 if (flFrametime < 1.0f)
9375 aip->time_enemy_in_range *= (1.0f - flFrametime);
9376 else
9377 aip->time_enemy_in_range = 0;
9378 }
9379
9380 if(has_fired == -1){
9381 ship_stop_fire_primary(Pl_objp);
9382 }
9383
9384 }
9385
9386 /**
9387 * Make the object *objp move so that the point *start moves towards the point *finish.
9388 * @return distance.
9389 */
dock_move_towards_point(object * objp,vec3d * start,vec3d * finish,float speed_scale,float other_obj_speed=0.0f,rotating_dockpoint_info * rdinfo=NULL)9390 float dock_move_towards_point(object *objp, vec3d *start, vec3d *finish, float speed_scale, float other_obj_speed = 0.0f, rotating_dockpoint_info *rdinfo = NULL)
9391 {
9392 physics_info *pi = &objp->phys_info;
9393 float dist; // dist to goal
9394 vec3d v2g; // vector to goal
9395
9396 dist = vm_vec_dist_quick(start, finish);
9397 if (dist > 0.0f) {
9398 float speed;
9399
9400 dist = vm_vec_normalized_dir(&v2g, finish, start);
9401 speed = fl_sqrt(dist) * speed_scale;
9402
9403 // Goober5000 - if we're on a rotating submodel and we're rotating with it, adjust velocity for rotation
9404 if (rdinfo && (rdinfo->submodel >= 0))
9405 {
9406 speed *= 1.25f;
9407
9408 switch (rdinfo->dock_mode)
9409 {
9410 case DOA_APPROACH:
9411 // ramp submodel's linear velocity according to distance
9412 speed += (rdinfo->submodel_r * rdinfo->submodel_w) / (dist);
9413 break;
9414
9415 case DOA_DOCK:
9416 case DOA_UNDOCK_1:
9417 // use docker's linear velocity and don't ramp
9418 speed += (vm_vec_dist(&rdinfo->submodel_pos, &objp->pos) * rdinfo->submodel_w);
9419 break;
9420 }
9421 }
9422
9423 if (other_obj_speed < MAX_REPAIR_SPEED*0.75f)
9424 speed += other_obj_speed;
9425 else
9426 speed += MAX_REPAIR_SPEED*0.75f;
9427
9428 vm_vec_copy_scale(&pi->desired_vel, &v2g, speed);
9429 } else
9430 vm_vec_zero(&pi->desired_vel);
9431
9432 return dist;
9433 }
9434
9435 // Set the orientation in the global reference frame for an object to attain
9436 // to dock with another object. Resultant global matrix returned in dom.
9437 // Revised by Goober5000
set_goal_dock_orient(matrix * dom,matrix * docker_dock_orient,matrix * docker_orient,matrix * dockee_dock_orient,matrix * dockee_orient)9438 void set_goal_dock_orient(matrix *dom, matrix *docker_dock_orient, matrix *docker_orient, matrix *dockee_dock_orient, matrix *dockee_orient)
9439 {
9440 vec3d fvec, uvec;
9441 matrix m1, m2, m3;
9442
9443 // Compute the global orientation of the dockee's docking bay.
9444
9445 // get the rotated (local) fvec
9446 vm_vec_rotate(&fvec, &dockee_dock_orient->vec.fvec, dockee_orient);
9447 vm_vec_negate(&fvec);
9448
9449 // get the rotated (local) uvec
9450 vm_vec_rotate(&uvec, &dockee_dock_orient->vec.uvec, dockee_orient);
9451
9452 // create a rotation matrix
9453 vm_vector_2_matrix(&m1, &fvec, &uvec, NULL);
9454
9455 // get the global orientation
9456 vm_matrix_x_matrix(&m3, dockee_orient, &m1);
9457
9458 // Compute the matrix given by the docker's docking bay.
9459
9460 // get the rotated (local) fvec
9461 vm_vec_rotate(&fvec, &docker_dock_orient->vec.fvec, docker_orient);
9462
9463 // get the rotated (local) uvec
9464 vm_vec_rotate(&uvec, &docker_dock_orient->vec.uvec, docker_orient);
9465
9466 // create a rotation matrix
9467 vm_vector_2_matrix(&m2, &fvec, &uvec, NULL);
9468
9469 // Pre-multiply the orientation of the source object (docker_orient) by the transpose
9470 // of the docking bay's orientation, ie unrotate the source object's matrix.
9471 vm_transpose(&m2);
9472 vm_matrix_x_matrix(dom, &m3, &m2);
9473 }
9474
9475 // Goober5000
9476 // Return the rotating submodel on which is mounted the specified dockpoint, or -1 for none.
find_parent_rotating_submodel(polymodel * pm,int dock_index)9477 int find_parent_rotating_submodel(polymodel *pm, int dock_index)
9478 {
9479 Assertion(pm != nullptr, "pm cannot be null!");
9480 Assertion(dock_index >= 0 && dock_index < pm->n_docks, "for model %s, dock_index %d must be >= 0 and < %d!", pm->filename, dock_index, pm->n_docks);
9481 int path_num, submodel;
9482
9483 // make sure we have a spline path to check against before going any further
9484 if (pm->docking_bays[dock_index].num_spline_paths <= 0)
9485 {
9486 return -1;
9487 }
9488
9489 // find a path for this dockpoint (c.f. ai_return_path_num_from_dockbay)
9490 path_num = pm->docking_bays[dock_index].splines[0];
9491
9492 // path must exist
9493 if ((path_num >= 0) && (path_num < pm->n_paths))
9494 {
9495 // find the submodel for the path for this dockpoint
9496 submodel = pm->paths[path_num].parent_submodel;
9497
9498 // submodel must exist and must move
9499 if ((submodel >= 0) && (submodel < pm->n_models) && (pm->submodel[submodel].movement_type >= 0))
9500 {
9501 return submodel;
9502 }
9503 }
9504
9505 // if path doesn't exist or the submodel doesn't exist or the submodel doesn't move
9506 return -1;
9507 }
9508
9509 // Goober5000
find_adjusted_dockpoint_info(vec3d * global_dock_point,matrix * global_dock_orient,object * objp,polymodel * pm,int submodel,int dock_index)9510 void find_adjusted_dockpoint_info(vec3d *global_dock_point, matrix *global_dock_orient, object *objp, polymodel *pm, int submodel, int dock_index)
9511 {
9512 Assertion(global_dock_point != nullptr && global_dock_orient != nullptr && objp != nullptr && pm != nullptr, "arguments cannot be null!");
9513 Assertion(dock_index >= 0 && dock_index < pm->n_docks, "for model %s, dock_index %d must be >= 0 and < %d!", pm->filename, dock_index, pm->n_docks);
9514
9515 vec3d fvec, uvec;
9516
9517 // are we basing this off a rotating submodel?
9518 if (submodel >= 0)
9519 {
9520 vec3d submodel_offset;
9521 vec3d local_p0, local_p1;
9522
9523 auto shipp = &Ships[objp->instance];
9524 auto pmi = model_get_instance(shipp->model_instance_num);
9525 Assert(pmi->model_num == pm->id);
9526
9527 // calculate the dockpoint locations relative to the unrotated submodel
9528 model_find_submodel_offset(&submodel_offset, pm, submodel);
9529 vm_vec_sub(&local_p0, &pm->docking_bays[dock_index].pnt[0], &submodel_offset);
9530 vm_vec_sub(&local_p1, &pm->docking_bays[dock_index].pnt[1], &submodel_offset);
9531
9532 // find the dynamic positions of the dockpoints
9533 vec3d global_p0, global_p1;
9534 model_instance_find_world_point(&global_p0, &local_p0, pm, pmi, submodel, &objp->orient, &objp->pos);
9535 model_instance_find_world_point(&global_p1, &local_p1, pm, pmi, submodel, &objp->orient, &objp->pos);
9536 vm_vec_avg(global_dock_point, &global_p0, &global_p1);
9537 vm_vec_normalized_dir(&uvec, &global_p1, &global_p0);
9538
9539 // find the normal of the first dockpoint
9540 model_instance_find_world_dir(&fvec, &pm->docking_bays[dock_index].norm[0], pm, pmi, submodel, &objp->orient);
9541
9542 vm_vector_2_matrix(global_dock_orient, &fvec, &uvec);
9543 }
9544 // use the static dockpoints
9545 else
9546 {
9547 vec3d local_point, local_uvec, local_fvec;
9548 vm_vec_avg(&local_point, &pm->docking_bays[dock_index].pnt[0], &pm->docking_bays[dock_index].pnt[1]);
9549 vm_vec_normalized_dir(&local_uvec, &pm->docking_bays[dock_index].pnt[1], &pm->docking_bays[dock_index].pnt[0]);
9550 local_fvec = pm->docking_bays[dock_index].norm[0];
9551
9552 vm_vec_unrotate(global_dock_point, &local_point, &objp->orient);
9553 vm_vec_unrotate(&uvec, &local_uvec, &objp->orient);
9554 vm_vec_unrotate(&fvec, &local_fvec, &objp->orient);
9555
9556 vm_vec_add2(global_dock_point, &objp->pos);
9557 vm_vector_2_matrix(global_dock_orient, &fvec, &uvec);
9558 }
9559 }
9560
9561
9562 // Make docker_objp dock with dockee_objp
9563 // Returns distance to goal, defined as distance between corresponding dock points, plus 10.0f * rotational velocity vector (DOA_DOCK only)
9564 // DOA_APPROACH means approach point aip->path_cur
9565 // DOA_DOCK means dock
9566 // DOA_UNDOCK_1 means undock, moving to point nearest dock bay
9567 // DOA_UNDOCK_2 means undock, moving to point nearest dock bay and facing away from ship
9568 // DOA_UNDOCK_3 means undock, moving directly away from ship
9569 // DOA_DOCK_STAY means rigidly maintain position in dock bay.
dock_orient_and_approach(object * docker_objp,int docker_index,object * dockee_objp,int dockee_index,int dock_mode,rotating_dockpoint_info * rdinfo)9570 float dock_orient_and_approach(object *docker_objp, int docker_index, object *dockee_objp, int dockee_index, int dock_mode, rotating_dockpoint_info *rdinfo)
9571 {
9572 ship_info *sip0, *sip1;
9573 polymodel *pm0, *pm1;
9574 ai_info *aip;
9575 matrix dom, out_orient;
9576 vec3d docker_point, dockee_point;
9577 float fdist = UNINITIALIZED_VALUE;
9578
9579 aip = &Ai_info[Ships[docker_objp->instance].ai_index];
9580
9581 docker_objp->phys_info.forward_thrust = 0.0f; // Kill thrust so we don't have a sputtering thruster.
9582
9583 // Goober5000 - moved out here to save calculations
9584 if (dock_mode != DOA_DOCK_STAY)
9585 if (ship_get_subsystem_strength(&Ships[docker_objp->instance], SUBSYSTEM_ENGINE) <= 0.0f)
9586 return 9999.9f;
9587
9588 // If dockee has moved much, then path will be recreated.
9589 // Might need to change state if moved too far.
9590 if ((dock_mode != DOA_DOCK_STAY) && (dock_mode != DOA_DOCK)) {
9591 maybe_recreate_path(docker_objp, aip, 0);
9592 }
9593
9594 sip0 = &Ship_info[Ships[docker_objp->instance].ship_info_index];
9595 sip1 = &Ship_info[Ships[dockee_objp->instance].ship_info_index];
9596 pm0 = model_get( sip0->model_num );
9597 pm1 = model_get( sip1->model_num );
9598
9599 Assert( docker_index >= 0 );
9600 Assert( dockee_index >= 0 );
9601
9602 Assert(pm0->docking_bays[docker_index].num_slots == 2);
9603 Assert(pm1->docking_bays[dockee_index].num_slots == 2);
9604
9605
9606 // Goober5000 - check if we're attached to a rotating submodel
9607 int dockee_rotating_submodel = find_parent_rotating_submodel(pm1, dockee_index);
9608
9609 matrix docker_dock_orient, dockee_dock_orient;
9610 // Goober5000 - move docking points with submodels if necessary, for both docker and dockee
9611 find_adjusted_dockpoint_info(&docker_point, &docker_dock_orient, docker_objp, pm0, -1, docker_index);
9612 find_adjusted_dockpoint_info(&dockee_point, &dockee_dock_orient, dockee_objp, pm1, dockee_rotating_submodel, dockee_index);
9613
9614 // Goober5000
9615 vec3d submodel_pos = ZERO_VECTOR;
9616 float submodel_radius = 0.0f;
9617 float submodel_omega = 0.0f;
9618 if ((dockee_rotating_submodel >= 0) && (dock_mode != DOA_DOCK_STAY))
9619 {
9620 vec3d submodel_offset;
9621
9622 // get submodel center
9623 model_find_submodel_offset(&submodel_offset, pm1, dockee_rotating_submodel);
9624 vm_vec_add(&submodel_pos, &dockee_objp->pos, &submodel_offset);
9625
9626 polymodel_instance *pmi1 = model_get_instance(Ships[dockee_objp->instance].model_instance_num);
9627
9628 // get angular velocity of dockpoint
9629 submodel_omega = pmi1->submodel[dockee_rotating_submodel].current_turn_rate;
9630
9631 // get radius to dockpoint
9632 submodel_radius = vm_vec_dist(&submodel_pos, &dockee_point);
9633 }
9634
9635 // Goober5000
9636 rotating_dockpoint_info rdinfo_buf;
9637 if (rdinfo == NULL)
9638 rdinfo = &rdinfo_buf;
9639
9640 rdinfo->docker_point = docker_point;
9641 rdinfo->dockee_point = dockee_point;
9642 rdinfo->dock_mode = dock_mode;
9643 rdinfo->submodel = dockee_rotating_submodel;
9644 rdinfo->submodel_pos = submodel_pos;
9645 rdinfo->submodel_r = submodel_radius;
9646 rdinfo->submodel_w = submodel_omega;
9647
9648
9649 float speed_scale = 1.0f;
9650 if (sip0->flags[Ship::Info_Flags::Support]) {
9651 speed_scale = 3.0f;
9652 }
9653
9654 switch (dock_mode) {
9655 case DOA_APPROACH:
9656 {
9657 vec3d *goal_point;
9658
9659 // Compute the desired global orientation matrix for the docker's station.
9660 // That is, the normal vector of the docking station must be the same as the
9661 // forward vector and the vector between its two points must be the uvec.
9662 set_goal_dock_orient(&dom, &docker_dock_orient, &docker_objp->orient, &dockee_dock_orient, &dockee_objp->orient);
9663
9664 // Compute new orientation matrix and update rotational velocity.
9665 vec3d omega_in, omega_out, vel_limit, acc_limit;
9666 float tdist, mdist, ss1;
9667
9668 omega_in = docker_objp->phys_info.rotvel;
9669 vel_limit = docker_objp->phys_info.max_rotvel;
9670 vm_vec_copy_scale(&acc_limit, &vel_limit, 0.3f);
9671
9672 if (sip0->flags[Ship::Info_Flags::Support])
9673 vm_vec_scale(&acc_limit, 2.0f);
9674
9675 if(Framerate_independent_turning)
9676 ai_compensate_for_retail_turning(&vel_limit, &acc_limit, docker_objp->phys_info.rotdamp, false);
9677
9678 // true at end of line prevent overshoot
9679 vm_angular_move_matrix(&dom, &docker_objp->orient, &omega_in, flFrametime, &out_orient, &omega_out, &vel_limit, &acc_limit, false, true);
9680
9681 if (Framerate_independent_turning) {
9682 docker_objp->phys_info.desired_rotvel = omega_out;
9683 docker_objp->phys_info.ai_desired_orient = out_orient;
9684 }
9685 else {
9686 docker_objp->phys_info.rotvel = omega_out;
9687 docker_objp->orient = out_orient;
9688 }
9689
9690 // Translate towards goal and note distance to goal.
9691 goal_point = &Path_points[aip->path_cur].pos;
9692 mdist = ai_matrix_dist(&docker_objp->orient, &dom);
9693 tdist = vm_vec_dist_quick(&docker_objp->pos, goal_point);
9694
9695 // If translation is badly lagging rotation, speed up translation.
9696 if (mdist > 0.1f) {
9697 ss1 = tdist/(10.0f * mdist);
9698 if (ss1 > 2.0f)
9699 ss1 = 2.0f;
9700 } else
9701 ss1 = 2.0f;
9702
9703 // if we're docking to a rotating submodel, speed up translation
9704 if (dockee_rotating_submodel >= 0)
9705 ss1 = 2.0f;
9706
9707 speed_scale *= 1.0f + ss1;
9708
9709 fdist = dock_move_towards_point(docker_objp, &docker_objp->pos, goal_point, speed_scale, dockee_objp->phys_info.speed, rdinfo);
9710
9711 // Note, we're interested in distance from goal, so if we're still turning, bash that into return value.
9712 fdist += 2.0f * mdist;
9713
9714 break;
9715 }
9716 case DOA_DOCK:
9717 case DOA_DOCK_STAY:
9718 {
9719 // Compute the desired global orientation matrix for the docker's station.
9720 // That is, the normal vector of the docking station must be the same as the
9721 // forward vector and the vector between its two points must be the uvec.
9722 set_goal_dock_orient(&dom, &docker_dock_orient, &docker_objp->orient, &dockee_dock_orient, &dockee_objp->orient);
9723
9724 // Compute distance between dock bay points.
9725 if (dock_mode == DOA_DOCK) {
9726 vec3d omega_in, omega_out, vel_limit, acc_limit;
9727
9728 // Compute new orientation matrix and update rotational velocity.
9729 omega_in = docker_objp->phys_info.rotvel;
9730 vel_limit = docker_objp->phys_info.max_rotvel;
9731 vm_vec_copy_scale(&acc_limit, &vel_limit, 0.3f);
9732
9733 if (sip0->flags[Ship::Info_Flags::Support])
9734 vm_vec_scale(&acc_limit, 2.0f);
9735
9736 if(Framerate_independent_turning)
9737 ai_compensate_for_retail_turning(&vel_limit, &acc_limit, docker_objp->phys_info.rotdamp, false);
9738
9739 vm_angular_move_matrix(&dom, &docker_objp->orient, &omega_in, flFrametime, &out_orient, &omega_out, &vel_limit, &acc_limit, false);
9740
9741 if (Framerate_independent_turning) {
9742 docker_objp->phys_info.desired_rotvel = omega_out;
9743 docker_objp->phys_info.ai_desired_orient = out_orient;
9744 }
9745 else {
9746 docker_objp->phys_info.rotvel = omega_out;
9747 docker_objp->orient = out_orient;
9748 }
9749
9750 fdist = dock_move_towards_point(docker_objp, &docker_point, &dockee_point, speed_scale, dockee_objp->phys_info.speed, rdinfo);
9751
9752 // Note, we're interested in distance from goal, so if we're still turning, bash that into return value.
9753 fdist += 10.0f * vm_vec_mag_quick(&omega_out);
9754 } else {
9755 Assert(dock_mode == DOA_DOCK_STAY);
9756 matrix temp, m_offset;
9757 vec3d origin_docker_point, adjusted_docker_point, v_offset;
9758
9759 // find out the rotation matrix that will get us from the old to the new rotation
9760 // (see Mantis #2787)
9761 vm_copy_transpose(&temp, &docker_objp->orient);
9762 vm_matrix_x_matrix(&m_offset, &temp, &dom);
9763
9764 // now find out the new docker point after being adjusted for the new orientation
9765 vm_vec_sub(&origin_docker_point, &docker_point, &docker_objp->pos);
9766 vm_vec_rotate(&adjusted_docker_point, &origin_docker_point, &m_offset);
9767 vm_vec_add2(&adjusted_docker_point, &docker_objp->pos);
9768
9769 // find the vector that will get us from the old to the new position
9770 vm_vec_sub(&v_offset, &dockee_point, &adjusted_docker_point);
9771
9772 // now set the new rotation and move to the new position
9773 docker_objp->orient = dom;
9774 vm_vec_add2(&docker_objp->pos, &v_offset);
9775 }
9776
9777 break;
9778 }
9779 case DOA_UNDOCK_1:
9780 {
9781 // Undocking.
9782 // Move to point on dock path nearest to dock station.
9783 Assert(aip->path_length >= 2);
9784 fdist = dock_move_towards_point(docker_objp, &docker_objp->pos, &Path_points[aip->path_start + aip->path_length-2].pos, speed_scale, 0.0f, rdinfo);
9785
9786 break;
9787 }
9788 case DOA_UNDOCK_2:
9789 {
9790 // Undocking.
9791 // Move to point on dock path nearest to dock station and orient away from big ship.
9792 int desired_index;
9793
9794 Assert(aip->path_length >= 2);
9795
9796 desired_index = aip->path_length-2;
9797 fdist = dock_move_towards_point(docker_objp, &docker_objp->pos, &Path_points[aip->path_start + desired_index].pos, speed_scale, 0.0f, rdinfo);
9798
9799 break;
9800 }
9801 case DOA_UNDOCK_3:
9802 {
9803 vec3d goal_point;
9804 float dist, goal_dist;
9805 vec3d away_vec;
9806
9807 goal_dist = docker_objp->radius + dockee_objp->radius + 25.0f;
9808
9809 dist = vm_vec_normalized_dir(&away_vec, &docker_objp->pos, &dockee_objp->pos);
9810 vm_vec_scale_add(&goal_point, &dockee_objp->pos, &away_vec, goal_dist);
9811 if (vm_vec_dist_quick(&goal_point, &dockee_objp->pos) < vm_vec_dist_quick(&docker_objp->pos, &dockee_objp->pos))
9812 fdist = 0.0f;
9813 else
9814 {
9815 float dot, accel;
9816 ai_turn_towards_vector(&goal_point, docker_objp, nullptr, nullptr, 0.0f, 0);
9817
9818 dot = vm_vec_dot(&docker_objp->orient.vec.fvec, &away_vec);
9819 accel = 0.1f;
9820 if (dot > accel)
9821 accel = dot;
9822 if (dist > goal_dist/2)
9823 accel *= 1.2f - 0.5f*goal_dist/dist;
9824
9825 accelerate_ship(aip, accel);
9826 fdist = vm_vec_dist_quick(&docker_objp->pos, &goal_point);
9827 }
9828
9829 break;
9830 }
9831 }
9832
9833 return fdist;
9834 }
9835
debug_find_guard_object()9836 void debug_find_guard_object()
9837 {
9838 ship *shipp = &Ships[Pl_objp->instance];
9839 object *objp;
9840
9841 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
9842 if ((Pl_objp != objp) && (objp->type == OBJ_SHIP)) {
9843 if (objp->instance != -1) {
9844 if (Ships[objp->instance].team == shipp->team) {
9845 ai_set_guard_object(Pl_objp, objp);
9846 }
9847 }
9848 }
9849 }
9850
9851 }
9852
9853 /**
9854 * Given an object number, return the number of ships attacking it.
9855 */
num_ships_attacking(int target_objnum)9856 int num_ships_attacking(int target_objnum)
9857 {
9858 object *attacking_objp;
9859 ai_info *attacking_aip;
9860 ship_obj *so;
9861 int count = 0;
9862 int target_team = obj_team(&Objects[target_objnum]);
9863
9864 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )
9865 {
9866 attacking_objp = &Objects[so->objnum];
9867 if (attacking_objp->instance < 0)
9868 continue;
9869
9870 attacking_aip = &Ai_info[Ships[attacking_objp->instance].ai_index];
9871
9872 // don't count instructor
9873 if (is_training_mission() && is_instructor(attacking_objp))
9874 continue; // Goober5000 10/06/2005 changed from break
9875
9876 if (iff_x_attacks_y(Ships[attacking_objp->instance].team, target_team))
9877 {
9878 if (attacking_aip->target_objnum == target_objnum)
9879 {
9880 if ( ((Game_mode & GM_MULTIPLAYER) && (attacking_objp->flags[Object::Object_Flags::Player_ship]))
9881 || (attacking_aip->mode == AIM_CHASE) )
9882 {
9883 count++;
9884 }
9885 }
9886 }
9887 }
9888
9889 return count;
9890 }
9891
9892 // For all objects attacking object #objnum, remove the one that is farthest away.
9893 // Do this by resuming previous behavior, if any. If not, set target_objnum to -1.
remove_farthest_attacker(int objnum)9894 void remove_farthest_attacker(int objnum)
9895 {
9896 object *objp, *objp2, *farthest_objp;
9897 ship_obj *so;
9898 float farthest_dist;
9899
9900 objp2 = &Objects[objnum];
9901
9902 farthest_dist = 9999999.9f;
9903 farthest_objp = NULL;
9904
9905 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
9906 objp = &Objects[so->objnum];
9907 if ( !(objp->flags[Object::Object_Flags::Player_ship])) {
9908 if (objp->instance != -1) {
9909 ai_info *aip2;
9910
9911 aip2 = &Ai_info[Ships[objp->instance].ai_index];
9912
9913 if ((aip2->mode == AIM_CHASE) && (aip2->target_objnum == objnum)) {
9914 if (iff_x_attacks_y(Ships[objp->instance].team, Ships[objp2->instance].team)) {
9915 float dist;
9916
9917 dist = vm_vec_dist_quick(&objp->pos, &objp2->pos);
9918 if (dist < farthest_dist) {
9919 farthest_dist = dist;
9920 farthest_objp = objp;
9921 }
9922 }
9923 }
9924 }
9925 }
9926 }
9927
9928 if (farthest_objp != NULL) {
9929 ai_info *aip;
9930 Assert(farthest_objp->type == OBJ_SHIP);
9931 Assert((farthest_objp->instance > -1) && (farthest_objp->instance < MAX_SHIPS));
9932 Assert(Ships[farthest_objp->instance].ai_index > -1);
9933
9934 aip = &Ai_info[Ships[farthest_objp->instance].ai_index];
9935
9936 if (!maybe_resume_previous_mode(Pl_objp, aip))
9937 {
9938 // If already ignoring something under player's orders, don't ignore current target.
9939 if ((aip->ignore_objnum == UNUSED_OBJNUM) || (aip->ai_flags[AI::AI_Flags::Temporary_ignore]))
9940 {
9941 aip->ignore_objnum = aip->target_objnum;
9942 aip->ignore_signature = Objects[aip->target_objnum].signature;
9943 aip->ai_flags.set(AI::AI_Flags::Temporary_ignore);
9944 aip->ignore_expire_timestamp = timestamp(Random::next(20, 29) * 1000); // OK to attack again in 20 to 29 seconds.
9945 }
9946 aip->target_objnum = -1;
9947 ai_do_default_behavior(farthest_objp);
9948 }
9949 }
9950 }
9951
9952 // Maybe limit the number of attackers on attack_objnum. For now, only limit attackers
9953 // in attacked_objnum is the player
9954 // input: attacked_objnum => object index for ship we want to limit attacks on
9955 //
9956 // exit: 1 => num attackers exceeds maximum, abort
9957 // 0 => removed the farthest attacker
9958 // -1 => nothing was done
ai_maybe_limit_attackers(int attacked_objnum)9959 int ai_maybe_limit_attackers(int attacked_objnum)
9960 {
9961 int rval=-1;
9962
9963 if ( Objects[attacked_objnum].flags[Object::Object_Flags::Player_ship]) {
9964 int num_attacking;
9965 num_attacking = num_ships_attacking(attacked_objnum);
9966
9967 if (num_attacking == The_mission.ai_profile->max_attackers[Game_skill_level]) {
9968 remove_farthest_attacker(attacked_objnum);
9969 rval=0;
9970 } else if (num_attacking > The_mission.ai_profile->max_attackers[Game_skill_level]) {
9971 rval=1;
9972 }
9973 }
9974
9975 return rval;
9976 }
9977
9978 /**
9979 * Object being guarded by object *guard_objp was hit by object *hitter_objp
9980 */
guard_object_was_hit(object * guard_objp,object * hitter_objp)9981 void guard_object_was_hit(object *guard_objp, object *hitter_objp)
9982 {
9983 int hitter_objnum;
9984 ai_info *aip;
9985
9986 aip = &Ai_info[Ships[guard_objp->instance].ai_index];
9987
9988 if (guard_objp == hitter_objp) {
9989 return;
9990 }
9991
9992 if (guard_objp->type == OBJ_GHOST || hitter_objp->type == OBJ_GHOST)
9993 return;
9994
9995 if (aip->ai_flags[AI::AI_Flags::No_dynamic]) // Not allowed to pursue dynamic goals. So, why are we guarding?
9996 return;
9997
9998 Assert( (hitter_objp->type == OBJ_SHIP) || (hitter_objp->type == OBJ_ASTEROID) || (hitter_objp->type == OBJ_WEAPON) );
9999
10000 hitter_objnum = OBJ_INDEX(hitter_objp);
10001
10002 if ( hitter_objp->type == OBJ_SHIP ) {
10003 // If the hitter object is the ignore object, don't attack it.
10004 if (is_ignore_object(aip, OBJ_INDEX(hitter_objp)))
10005 return;
10006
10007 // If hitter is on same team as me, don't attack him.
10008 if (Ships[guard_objp->instance].team == Ships[hitter_objp->instance].team)
10009 return;
10010
10011 // limit the number of ships attacking hitter_objnum (for now, only if hitter_objnum is player)
10012 if ( ai_maybe_limit_attackers(hitter_objnum) == 1 ) {
10013 return;
10014 }
10015
10016 // don't attack if you can't see him
10017 if ( awacs_get_level(hitter_objp, &Ships[aip->shipnum], 1) < 1 ) {
10018 // if he's a stealth and visible, but not targetable, ok to attack.
10019 if ( is_object_stealth_ship(hitter_objp) ) {
10020 if ( ai_is_stealth_visible(guard_objp, hitter_objp) != STEALTH_IN_FRUSTUM ) {
10021 return;
10022 }
10023 }
10024 }
10025 }
10026
10027 if (aip->target_objnum == -1) {
10028 aip->ok_to_target_timestamp = timestamp(0);
10029 }
10030
10031 if ((aip->submode == AIS_GUARD_PATROL) || (aip->submode == AIS_GUARD_STATIC)) {
10032
10033 if ( hitter_objp->type == OBJ_SHIP ) {
10034 if (!(Ship_info[Ships[guard_objp->instance].ship_info_index].is_small_ship())) {
10035 return;
10036 }
10037
10038 // limit the number of ships attacking hitter_objnum (for now, only if hitter_objnum is player)
10039 if ( ai_maybe_limit_attackers(hitter_objnum) == 1 ) {
10040 return;
10041 }
10042 }
10043
10044 if (aip->target_objnum != hitter_objnum) {
10045 aip->aspect_locked_time = 0.0f;
10046 }
10047
10048 aip->ok_to_target_timestamp = timestamp(0);
10049
10050 set_target_objnum(aip, hitter_objnum);
10051 aip->previous_mode = AIM_GUARD;
10052 aip->previous_submode = aip->submode;
10053 aip->mode = AIM_CHASE;
10054 aip->submode = SM_ATTACK;
10055 aip->submode_start_time = Missiontime;
10056 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
10057 } else if (aip->previous_mode == AIM_GUARD) {
10058 if (aip->target_objnum == -1) {
10059
10060 if ( hitter_objp->type == OBJ_SHIP ) {
10061 // limit the number of ships attacking hitter_objnum (for now, only if hitter_objnum is player)
10062 if ( ai_maybe_limit_attackers(hitter_objnum) == 1 ) {
10063 return;
10064 }
10065 }
10066
10067 set_target_objnum(aip, hitter_objnum);
10068 aip->mode = AIM_CHASE;
10069 aip->submode = SM_ATTACK;
10070 aip->submode_start_time = Missiontime;
10071 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
10072 } else {
10073 int num_attacking_cur, num_attacking_new;
10074
10075 num_attacking_cur = num_ships_attacking(aip->target_objnum);
10076 if (num_attacking_cur > 1) {
10077 num_attacking_new = num_ships_attacking(hitter_objnum);
10078
10079 if (num_attacking_new < num_attacking_cur) {
10080
10081 if ( hitter_objp->type == OBJ_SHIP ) {
10082 // limit the number of ships attacking hitter_objnum (for now, only if hitter_objnum is player)
10083 if ( ai_maybe_limit_attackers(hitter_objnum) == 1 ) {
10084 return;
10085 }
10086 }
10087 set_target_objnum(aip, OBJ_INDEX(hitter_objp));
10088 aip->mode = AIM_CHASE;
10089 aip->submode = SM_ATTACK;
10090 aip->submode_start_time = Missiontime;
10091 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
10092 }
10093 }
10094 }
10095 }
10096 }
10097
10098 // Ship object *hit_objp was hit by ship object *hitter_objp.
10099 // See if anyone is guarding hit_objp and, if so, do something useful.
maybe_update_guard_object(object * hit_objp,object * hitter_objp)10100 void maybe_update_guard_object(object *hit_objp, object *hitter_objp)
10101 {
10102 object *objp;
10103 ship_obj *so;
10104
10105 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
10106 objp = &Objects[so->objnum];
10107 if (objp->instance != -1) {
10108 ai_info *aip;
10109 aip = &Ai_info[Ships[objp->instance].ai_index];
10110
10111 if ((aip->mode == AIM_GUARD) || (aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC)) {
10112 // Only attack ship if allowed to
10113 ship *eshipp = &Ships[hitter_objp->instance];
10114 if ((Ship_info[eshipp->ship_info_index].class_type >= 0 && (Ship_types[Ship_info[eshipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_guards_attack]))) {
10115 if (aip->guard_objnum == OBJ_INDEX(hit_objp)) {
10116 guard_object_was_hit(objp, hitter_objp);
10117 } else if ((aip->guard_wingnum != -1) && (aip->guard_wingnum == Ai_info[Ships[hit_objp->instance].ai_index].wing)) {
10118 guard_object_was_hit(objp, hitter_objp);
10119 }
10120 }
10121 }
10122 }
10123 }
10124 }
10125
10126 // Scan missile list looking for bombs homing on guarded_objp
10127 // return 1 if bomb is found (and targeted by guarding_objp), otherwise return 0
ai_guard_find_nearby_bomb(object * guarding_objp,object * guarded_objp)10128 int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)
10129 {
10130 missile_obj *mo;
10131 object *bomb_objp, *closest_bomb_objp=NULL;
10132 float dist, dist_to_guarding_obj,closest_dist_to_guarding_obj=999999.0f;
10133 weapon *wp;
10134 weapon_info *wip;
10135
10136 for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
10137 Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
10138 bomb_objp = &Objects[mo->objnum];
10139
10140 wp = &Weapons[bomb_objp->instance];
10141 wip = &Weapon_info[wp->weapon_info_index];
10142
10143 if ( !((wip->wi_flags[Weapon::Info_Flags::Bomb]) || (wip->wi_flags[Weapon::Info_Flags::Fighter_Interceptable])) ) {
10144 continue;
10145 }
10146
10147 if ( wp->homing_object != guarded_objp ) {
10148 continue;
10149 }
10150
10151 if (wp->lssm_stage==3){
10152 continue;
10153 }
10154
10155 dist = vm_vec_dist_quick(&bomb_objp->pos, &guarded_objp->pos);
10156
10157 if (dist < (MAX_GUARD_DIST + guarded_objp->radius)*3) {
10158 dist_to_guarding_obj = vm_vec_dist_quick(&bomb_objp->pos, &guarding_objp->pos);
10159 if ( dist_to_guarding_obj < closest_dist_to_guarding_obj ) {
10160 closest_dist_to_guarding_obj = dist_to_guarding_obj;
10161 closest_bomb_objp = bomb_objp;
10162 }
10163 }
10164 }
10165
10166 if ( closest_bomb_objp ) {
10167 guard_object_was_hit(guarding_objp, closest_bomb_objp);
10168 return 1;
10169 }
10170
10171 return 0;
10172 }
10173
10174 /**
10175 * Scan enemy ships and see if one is near enough to guard object to be pursued.
10176 */
ai_guard_find_nearby_ship(object * guarding_objp,object * guarded_objp)10177 void ai_guard_find_nearby_ship(object *guarding_objp, object *guarded_objp)
10178 {
10179 ship *guarding_shipp = &Ships[guarding_objp->instance];
10180 ai_info *guarding_aip = &Ai_info[guarding_shipp->ai_index];
10181 ship_obj *so;
10182 object *enemy_objp;
10183 float dist;
10184
10185 for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
10186 {
10187 enemy_objp = &Objects[so->objnum];
10188
10189 if (enemy_objp->instance < 0)
10190 {
10191 continue;
10192 }
10193
10194 ship *eshipp = &Ships[enemy_objp->instance];
10195
10196 if (iff_x_attacks_y(guarding_shipp->team, eshipp->team))
10197 {
10198 // Don't attack a cargo container or other harmless ships
10199 if (Ship_info[eshipp->ship_info_index].class_type >= 0 && (Ship_types[Ship_info[eshipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_guards_attack]))
10200 {
10201 dist = vm_vec_dist_quick(&enemy_objp->pos, &guarded_objp->pos);
10202 if (dist < (MAX_GUARD_DIST + guarded_objp->radius)*3)
10203 {
10204 guard_object_was_hit(guarding_objp, enemy_objp);
10205 }
10206 else if ((dist < 3000.0f) && (Ai_info[eshipp->ai_index].target_objnum == guarding_aip->guard_objnum))
10207 {
10208 guard_object_was_hit(guarding_objp, enemy_objp);
10209 }
10210 }
10211 }
10212 }
10213 }
10214
10215 // Scan for nearby asteroids. Favor asteroids which have their collide_objnum set to that of the
10216 // guarded ship. Also, favor asteroids that are closer to the guarding ship, since it looks cooler
10217 // when a ship blows up an asteroid then goes after the pieces that break off.
ai_guard_find_nearby_asteroid(object * guarding_objp,object * guarded_objp)10218 void ai_guard_find_nearby_asteroid(object *guarding_objp, object *guarded_objp)
10219 {
10220 float dist;
10221
10222 object *closest_asteroid_objp=NULL, *danger_asteroid_objp=NULL, *asteroid_objp;
10223 float dist_to_self, closest_danger_asteroid_dist=999999.0f, closest_asteroid_dist=999999.0f;
10224
10225 for ( asteroid_objp = GET_FIRST(&obj_used_list); asteroid_objp != END_OF_LIST(&obj_used_list); asteroid_objp = GET_NEXT(asteroid_objp) ) {
10226 if ( asteroid_objp->type == OBJ_ASTEROID ) {
10227 // Attack asteroid if near guarded ship
10228 dist = vm_vec_dist_quick(&asteroid_objp->pos, &guarded_objp->pos);
10229 if ( dist < (MAX_GUARD_DIST + guarded_objp->radius)*2) {
10230 dist_to_self = vm_vec_dist_quick(&asteroid_objp->pos, &guarding_objp->pos);
10231 if ( OBJ_INDEX(guarded_objp) == asteroid_collide_objnum(asteroid_objp) ) {
10232 if( dist_to_self < closest_danger_asteroid_dist ) {
10233 danger_asteroid_objp=asteroid_objp;
10234 closest_danger_asteroid_dist=dist_to_self;
10235 }
10236 }
10237 if ( dist_to_self < closest_asteroid_dist ) {
10238 // only attack if moving slower than own max speed
10239 if ( vm_vec_mag_quick(&asteroid_objp->phys_info.vel) < guarding_objp->phys_info.max_vel.xyz.z ) {
10240 closest_asteroid_dist = dist_to_self;
10241 closest_asteroid_objp = asteroid_objp;
10242 }
10243 }
10244 }
10245 }
10246 }
10247
10248 if ( danger_asteroid_objp ) {
10249 guard_object_was_hit(guarding_objp, danger_asteroid_objp);
10250 } else if ( closest_asteroid_objp ) {
10251 guard_object_was_hit(guarding_objp, closest_asteroid_objp);
10252 }
10253 }
10254
10255 /**
10256 * Scan potential harmful objects and see if one is near enough to guard object to be pursued.
10257 */
ai_guard_find_nearby_object()10258 void ai_guard_find_nearby_object()
10259 {
10260 ship *shipp = &Ships[Pl_objp->instance];
10261 ai_info *aip = &Ai_info[shipp->ai_index];
10262 object *guardobjp;
10263 int bomb_found=0;
10264
10265 guardobjp = &Objects[aip->guard_objnum];
10266
10267 // highest priority is a bomb fired on guarded ship
10268 bomb_found = ai_guard_find_nearby_bomb(Pl_objp, guardobjp);
10269
10270 if ( !bomb_found ) {
10271 // check for ships if there are no bombs fired at guarded ship
10272 ai_guard_find_nearby_ship(Pl_objp, guardobjp);
10273
10274 // if not attacking anything, go for asteroid close to guarded ship
10275 if ( (aip->target_objnum == -1) && asteroid_count() ) {
10276 ai_guard_find_nearby_asteroid(Pl_objp, guardobjp);
10277 }
10278 }
10279 }
10280
10281 /**
10282 * Gets closest point on extended axis of cylinder, r_vec, and radius of cylinder
10283 * @return z of axis_point in cyl_objp reference frame
10284 */
get_cylinder_points(object * other_objp,object * cyl_objp,vec3d * axis_pt,vec3d * r_vec,float * radius)10285 float get_cylinder_points(object *other_objp, object *cyl_objp, vec3d *axis_pt, vec3d *r_vec, float *radius)
10286 {
10287 Assert(other_objp->type == OBJ_SHIP);
10288 Assert(cyl_objp->type == OBJ_SHIP);
10289
10290 // get radius of cylinder
10291 polymodel *pm = model_get(Ship_info[Ships[cyl_objp->instance].ship_info_index].model_num);
10292 float tempx, tempy;
10293 tempx = MAX(-pm->mins.xyz.x, pm->maxs.xyz.x);
10294 tempy = MAX(-pm->mins.xyz.y, pm->maxs.xyz.y);
10295 *radius = MAX(tempx, tempy);
10296
10297 // get vec from cylinder to other_obj
10298 vec3d r_sph;
10299 vm_vec_sub(&r_sph, &other_objp->pos, &cyl_objp->pos);
10300
10301 // get point on axis and on cylinder
10302 // extended_cylinder_z is along extended cylinder
10303 // cylinder_z is capped within cylinder
10304 float extended_cylinder_z = vm_vec_dot(&r_sph, &cyl_objp->orient.vec.fvec);
10305
10306 // get pt on axis of extended cylinder
10307 vm_vec_scale_add(axis_pt, &cyl_objp->pos, &cyl_objp->orient.vec.fvec, extended_cylinder_z);
10308
10309 // get r_vec (pos - axis_pt) normalized
10310 vm_vec_normalized_dir(r_vec, &other_objp->pos, axis_pt);
10311
10312 return extended_cylinder_z;
10313 }
10314
10315 // handler for guard behavior when guarding BIG ships
10316 // When someone has attacked guarded ship, then attack that ship.
10317 // To attack another ship, switch out of guard mode into chase mode.
ai_big_guard()10318 void ai_big_guard()
10319 {
10320
10321 ship *shipp = &Ships[Pl_objp->instance];
10322 ai_info *aip = &Ai_info[shipp->ai_index];
10323 object *guard_objp;
10324
10325 // sanity checks already done in ai_guard()
10326 guard_objp = &Objects[aip->guard_objnum];
10327
10328 switch (aip->submode) {
10329 case AIS_GUARD_STATIC:
10330 case AIS_GUARD_PATROL:
10331 {
10332 vec3d axis_pt, r_vec, theta_vec;
10333 float radius, extended_z;
10334
10335 // get random [0 to 1] based on OBJNUM
10336 float objval = static_randf(OBJ_INDEX(Pl_objp));
10337
10338 // get position relative to cylinder of guard_objp
10339 extended_z = get_cylinder_points(Pl_objp, guard_objp, &axis_pt, &r_vec, &radius);
10340 vm_vec_cross(&theta_vec, &guard_objp->orient.vec.fvec, &r_vec);
10341
10342 // half ships circle each way
10343 if (objval > 0.5f) {
10344 vm_vec_negate(&theta_vec);
10345 }
10346
10347 float min_guard_dist = radius + Pl_objp->radius + 50.0f;
10348 float desired_guard_dist = min_guard_dist + 0.5f * ((1.0f + objval) * MAX_GUARD_DIST);
10349 float max_guard_dist = min_guard_dist + 1.0f * ((1.0f + objval) * MAX_GUARD_DIST);
10350
10351 // get z extents
10352 float min_z, max_z, length;
10353 polymodel *pm = model_get(Ship_info[Ships[guard_objp->instance].ship_info_index].model_num);
10354 min_z = pm->mins.xyz.z;
10355 max_z = pm->maxs.xyz.z;
10356 length = max_z - min_z;
10357
10358 // get desired z
10359 // how often to choose new desired_z
10360 // 1*(64) sec < 2000, 2*(64) < 2-4000 3*(64) > 4-8000, etc (Missiontime >> 22 is 64 sec intervals)
10361 int time_choose = int(floor(log(length * 0.001f) / log(2.0f)));
10362 float desired_z = min_z + length * static_randf( (OBJ_INDEX(Pl_objp)) ^ (Missiontime >> (22 + time_choose)) );
10363
10364 // get r from guard_ship
10365 float cur_guard_rad = vm_vec_dist(&Pl_objp->pos, &axis_pt);
10366
10367 // is ship within extents of cylinder of ship it is guarding
10368 int inside = (extended_z > min_z) && (extended_z < min_z + length);
10369
10370 vec3d goal_pt;
10371 // maybe go into orbit mode
10372 if (cur_guard_rad < max_guard_dist) {
10373 if ( cur_guard_rad > min_guard_dist ) {
10374 if (inside) {
10375 // orbit
10376 vm_vec_scale_add(&goal_pt, &axis_pt, &r_vec, desired_guard_dist);
10377 vm_vec_scale_add2(&goal_pt, &theta_vec, desired_guard_dist);
10378 } else {
10379 // move to where I can orbit
10380 if (extended_z < min_z) {
10381 vm_vec_scale_add(&goal_pt, &guard_objp->pos, &guard_objp->orient.vec.fvec, min_z);
10382 } else {
10383 vm_vec_scale_add(&goal_pt, &guard_objp->pos, &guard_objp->orient.vec.fvec, max_z);
10384 }
10385 vm_vec_scale_add2(&goal_pt, &r_vec, desired_guard_dist);
10386 vm_vec_scale_add2(&goal_pt, &theta_vec, desired_guard_dist);
10387 }
10388 } else {
10389 // too close for orbit mode
10390 if (inside) {
10391 // inside (fly straight out and return circle)
10392 vm_vec_scale_add(&goal_pt, &axis_pt, &r_vec, max_guard_dist);
10393 } else {
10394 // outside (fly to edge and circle)
10395 if (extended_z < min_z) {
10396 vm_vec_scale_add(&goal_pt, &guard_objp->pos, &guard_objp->orient.vec.fvec, min_z);
10397 } else {
10398 vm_vec_scale_add(&goal_pt, &guard_objp->pos, &guard_objp->orient.vec.fvec, max_z);
10399 }
10400 vm_vec_scale_add2(&goal_pt, &r_vec, max_guard_dist);
10401 vm_vec_scale_add2(&goal_pt, &theta_vec, desired_guard_dist);
10402 }
10403 }
10404
10405 // make sure we have a forward velocity worth calculating (values too low can produce NaN in goal_pt) - taylor
10406 if (Pl_objp->phys_info.fspeed > 0.0001f) {
10407 // modify goal_pt to take account moving guard objp
10408 float dist = vm_vec_dist_quick(&Pl_objp->pos, &goal_pt);
10409 float time = dist / Pl_objp->phys_info.fspeed;
10410 vm_vec_scale_add2(&goal_pt, &guard_objp->phys_info.vel, time);
10411
10412 // now modify to move to desired z (at a max of 20 m/s)
10413 float delta_z = desired_z - extended_z;
10414 float v_z = delta_z * 0.2f;
10415 if (v_z < -20) {
10416 v_z = -20.0f;
10417 } else if (v_z > 20) {
10418 v_z = 20.0f;
10419 }
10420
10421 vm_vec_scale_add2(&goal_pt, &guard_objp->orient.vec.fvec, v_z*time);
10422 }
10423
10424 } else {
10425 // cast vector to center of guard_ship adjusted by desired_z
10426 float delta_z = desired_z - extended_z;
10427 vm_vec_scale_add(&goal_pt, &guard_objp->pos, &guard_objp->orient.vec.fvec, delta_z);
10428 }
10429
10430 // try not to bump into things along the way
10431 if ( (cur_guard_rad > max_guard_dist) || (extended_z < min_z) || (extended_z > max_z) ) {
10432 if (maybe_avoid_big_ship(Pl_objp, guard_objp, aip, &goal_pt, 5.0f)) {
10433 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10434 afterburners_stop(Pl_objp);
10435 }
10436 return;
10437 }
10438
10439 if (avoid_player(Pl_objp, &goal_pt)) {
10440 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10441 afterburners_stop(Pl_objp);
10442 }
10443 return;
10444 }
10445 } else {
10446 if (maybe_avoid_big_ship(Pl_objp, guard_objp, aip, &goal_pt, 5.0f)) {
10447 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10448 afterburners_stop(Pl_objp);
10449 }
10450 return;
10451 }
10452 }
10453
10454 // got the point, now let's go there
10455 ai_turn_towards_vector(&goal_pt, Pl_objp, nullptr, nullptr, 0.0f, 0);
10456 accelerate_ship(aip, 1.0f);
10457
10458 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]) && (cur_guard_rad > 1.1f * max_guard_dist)) {
10459 vec3d v2g;
10460 float dot_to_goal_point;
10461
10462 vm_vec_normalized_dir(&v2g, &goal_pt, &Pl_objp->pos);
10463 dot_to_goal_point = vm_vec_dot(&v2g, &Pl_objp->orient.vec.fvec);
10464
10465 if (ai_maybe_fire_afterburner(Pl_objp, aip) && dot_to_goal_point > 0.75f) {
10466 afterburners_start(Pl_objp);
10467 aip->afterburner_stop_time = Missiontime + 3*F1_0;
10468 }
10469 } else if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10470 afterburners_stop(Pl_objp);
10471 }
10472
10473 // Periodically, scan for a nearby ship to attack.
10474 if (((AI_FrameCount ^ (OBJ_INDEX(Pl_objp))) & 0x07) == 0) {
10475 ai_guard_find_nearby_object();
10476 }
10477 }
10478 break;
10479
10480 case AIS_GUARD_ATTACK:
10481 // The guarded ship has been attacked. Do something useful!
10482 ai_chase();
10483 break;
10484
10485 default:
10486 Int3(); // Illegal submode for Guard mode.
10487 // AL 06/03/97 comment out Int3() to allow milestone to get out the door
10488 aip->submode = AIS_GUARD_PATROL;
10489 aip->submode_start_time = Missiontime;
10490 break;
10491 }
10492 }
10493
10494 // Main handler for guard behavior.
10495 // When someone has attacked guarded ship, then attack that ship.
10496 // To attack another ship, switch out of guard mode into chase mode.
ai_guard()10497 void ai_guard()
10498 {
10499 ship *shipp = &Ships[Pl_objp->instance];
10500 ai_info *aip = &Ai_info[shipp->ai_index];
10501 object *guard_objp;
10502 float dist_to_guardobj;
10503 vec3d vec_to_guardobj;
10504
10505 if (aip->guard_objnum == -1) {
10506 aip->mode = AIM_NONE;
10507 return;
10508 }
10509
10510 Assert(aip->guard_objnum != -1);
10511
10512 guard_objp = &Objects[aip->guard_objnum];
10513
10514 if (guard_objp == Pl_objp) {
10515 Int3(); // This seems illegal. Why is a ship guarding itself?
10516 aip->guard_objnum = -1;
10517 return;
10518 }
10519
10520 // check that I have someone to guard
10521 if (guard_objp->instance == -1) {
10522 return;
10523 }
10524
10525 // Not sure whether this should be impossible, or a reasonable cleanup condition.
10526 // For now (3/31/97), it's getting trapped by an Assert, so clean it up.
10527 if (guard_objp->type != OBJ_SHIP) {
10528 aip->guard_objnum = -1;
10529 return;
10530 }
10531
10532 // handler for gurad object with BIG radius
10533 if (guard_objp->radius > BIG_GUARD_RADIUS) {
10534 ai_big_guard();
10535 return;
10536 }
10537
10538 float objval;
10539 vec3d goal_point;
10540 vec3d rel_vec;
10541 float dist_to_goal_point, dot_to_goal_point, accel_scale;
10542 vec3d v2g, rvec;
10543
10544 // get random [0 to 1] based on OBJNUM
10545 objval = static_randf(OBJ_INDEX(Pl_objp));
10546
10547 switch (aip->submode) {
10548 case AIS_GUARD_STATIC:
10549 case AIS_GUARD_PATROL:
10550 // Stay near ship
10551 dist_to_guardobj = vm_vec_normalized_dir(&vec_to_guardobj, &guard_objp->pos, &Pl_objp->pos);
10552
10553 rel_vec = aip->guard_vec;
10554 vm_vec_add(&goal_point, &guard_objp->pos, &rel_vec);
10555
10556 vm_vec_normalized_dir(&v2g, &goal_point, &Pl_objp->pos);
10557 dist_to_goal_point = vm_vec_dist_quick(&goal_point, &Pl_objp->pos);
10558 dot_to_goal_point = vm_vec_dot(&v2g, &Pl_objp->orient.vec.fvec);
10559 accel_scale = (1.0f + dot_to_goal_point)/2.0f;
10560
10561 // If far away, get closer
10562 if (dist_to_goal_point > MAX_GUARD_DIST + 1.5 * (Pl_objp->radius + guard_objp->radius)) {
10563 if (maybe_avoid_big_ship(Pl_objp, guard_objp, aip, &goal_point, 5.0f)) {
10564 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10565 afterburners_stop(Pl_objp);
10566 }
10567 return;
10568 }
10569
10570 if (avoid_player(Pl_objp, &goal_point)) {
10571 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10572 afterburners_stop(Pl_objp);
10573 }
10574 return;
10575 }
10576
10577 // quite far away, so try to go straight to
10578 compute_desired_rvec(&rvec, &goal_point, &Pl_objp->pos);
10579 ai_turn_towards_vector(&goal_point, Pl_objp, nullptr, nullptr, 0.0f, 0, &rvec);
10580
10581 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_scale * (0.25f + dist_to_goal_point/700.0f) > 0.8f)) {
10582 if (ai_maybe_fire_afterburner(Pl_objp, aip)) {
10583 afterburners_start(Pl_objp);
10584 aip->afterburner_stop_time = Missiontime + 3*F1_0;
10585 }
10586 accelerate_ship(aip, 1.0f);
10587 } else {
10588 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10589 afterburners_stop(Pl_objp);
10590 }
10591 accelerate_ship(aip, accel_scale * (0.25f + dist_to_goal_point/700.0f));
10592 }
10593
10594 } else {
10595 if (maybe_avoid_big_ship(Pl_objp, guard_objp, aip, &goal_point, 2.0f)) {
10596 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10597 afterburners_stop(Pl_objp);
10598 }
10599 return;
10600 }
10601
10602 // get max of guard_objp (1) normal speed (2) dock speed
10603 float speed = guard_objp->phys_info.speed;
10604
10605 if (guard_objp->type == OBJ_SHIP) {
10606 if (object_is_docked(guard_objp)) {
10607 speed = dock_calc_docked_speed(guard_objp);
10608 }
10609 }
10610
10611 // Deal with guarding a small object.
10612 // If going to guard_vec might cause a collision with guarded object, pick a new guard point.
10613 if (vm_vec_dot(&v2g, &vec_to_guardobj) > 0.8f) {
10614 if (dist_to_guardobj < dist_to_goal_point) {
10615 ai_set_guard_vec(Pl_objp, guard_objp); // OK to return here.
10616 return;
10617 }
10618 }
10619
10620 if (speed > 10.0f) {
10621 // If goal ship is moving more than a tiny bit, don't orbit it, get near it.
10622 if (vm_vec_dist_quick(&goal_point, &Pl_objp->pos) > 40.0f) {
10623 if (vm_vec_dot(&Pl_objp->orient.vec.fvec, &v2g) < 0.0f) {
10624 // Just slow down, don't turn.
10625 set_accel_for_target_speed(Pl_objp, guard_objp->phys_info.speed - dist_to_goal_point/10.0f);
10626 } else {
10627 // Goal point is in front.
10628 // If close to goal point, don't change direction, just change speed.
10629 if (dist_to_goal_point > Pl_objp->radius + 10.0f) {
10630 turn_towards_point(Pl_objp, &goal_point, NULL, 0.0f);
10631 }
10632
10633 set_accel_for_target_speed(Pl_objp, guard_objp->phys_info.speed + (dist_to_goal_point-40.0f)/20.0f);
10634 }
10635 } else {
10636 if (dot_to_goal_point > 0.8f) {
10637 turn_towards_point(Pl_objp, &goal_point, NULL, 0.0f);
10638 set_accel_for_target_speed(Pl_objp, guard_objp->phys_info.speed + dist_to_goal_point*0.1f);
10639 } else {
10640 set_accel_for_target_speed(Pl_objp, guard_objp->phys_info.speed - dist_to_goal_point*0.1f - 1.0f);
10641 }
10642 }
10643 // consider guard object STILL
10644 } else if (guard_objp->radius < 50.0f) {
10645 if (dist_to_goal_point > 15.0f) {
10646 turn_towards_point(Pl_objp, &goal_point, NULL, 0.0f);
10647 set_accel_for_target_speed(Pl_objp, (dist_to_goal_point-10.0f)/2.0f);
10648 } else if (Pl_objp->phys_info.speed < 1.0f) {
10649 turn_away_from_point(Pl_objp, &guard_objp->pos, 0.0f);
10650 }
10651 // It's a big ship
10652 } else if (dist_to_guardobj > MAX_GUARD_DIST + Pl_objp->radius + guard_objp->radius) {
10653 // Orbiting ship, too far away
10654 float dot = turn_towards_tangent(Pl_objp, &guard_objp->pos, (1.0f + objval/2) * guard_objp->radius);
10655 accelerate_ship(aip, (1.0f + dot)/2.0f);
10656 } else if (dist_to_guardobj < Pl_objp->radius + guard_objp->radius) {
10657 // Orbiting ship, got too close
10658 turn_away_from_point(Pl_objp, &guard_objp->pos, 0.0f);
10659 if ((dist_to_guardobj > guard_objp->radius + Pl_objp->radius + 50.0f) && (guard_objp->phys_info.speed > Pl_objp->phys_info.speed - 1.0f))
10660 change_acceleration(aip, 0.25f);
10661 else
10662 accelerate_ship(aip, 0.5f + objval/4.0f);
10663 } else {
10664 // Orbiting ship, about the right distance away.
10665 float dot = turn_towards_tangent(Pl_objp, &guard_objp->pos, (1.5f + objval/2.0f)*guard_objp->radius);
10666 if ((dist_to_guardobj > guard_objp->radius + Pl_objp->radius + 50.0f) && (guard_objp->phys_info.speed > Pl_objp->phys_info.speed - 1.0f))
10667 set_accel_for_target_speed(Pl_objp, (0.5f * (1.0f + dot)) * (guard_objp->phys_info.speed + (dist_to_guardobj - guard_objp->radius - Pl_objp->radius)/10.0f));
10668 else
10669 accelerate_ship(aip, 0.5f * (1.0f + dot) * (0.3f + objval/3.0f));
10670 }
10671 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) {
10672 afterburners_stop(Pl_objp);
10673 }
10674 }
10675
10676 // Periodically, scan for a nearby ship to attack.
10677 if (((AI_FrameCount ^ (OBJ_INDEX(Pl_objp))) & 0x07) == 0) {
10678 ai_guard_find_nearby_object();
10679 }
10680 break;
10681
10682 case AIS_GUARD_ATTACK:
10683 // The guarded ship has been attacked. Do something useful!
10684 ai_chase();
10685
10686 break;
10687 default:
10688 Int3(); // Illegal submode for Guard mode.
10689 // AL 06/03/97 comment out Int3() to allow milestone to get out the door
10690 aip->submode = AIS_GUARD_PATROL;
10691 aip->submode_start_time = Missiontime;
10692 break;
10693 }
10694
10695 }
10696
10697 // function to clean up ai flags, variables, and other interesting information
10698 // for a ship that was getting repaired. The how parameter is useful for multiplayer
10699 // only in that it tells us why the repaired ship is being cleaned up.
ai_do_objects_repairing_stuff(object * repaired_objp,object * repair_objp,int how)10700 void ai_do_objects_repairing_stuff( object *repaired_objp, object *repair_objp, int how )
10701 {
10702 ai_info *aip, *repair_aip;
10703 int stamp = -1;
10704
10705 int p_index;
10706 int p_team;
10707
10708 p_index = -1;
10709 p_team = -1;
10710
10711 // repaired_objp should not be null, but repair_objp will be null when a support ship is just warping in
10712 Assert(repaired_objp != NULL);
10713
10714 Assert( repaired_objp->type == OBJ_SHIP);
10715 aip = &Ai_info[Ships[repaired_objp->instance].ai_index];
10716
10717 if(Game_mode & GM_MULTIPLAYER){
10718 p_index = multi_find_player_by_object(repaired_objp);
10719 if (p_index >= 0) {
10720 p_team = Net_players[p_index].p_info.team;
10721 }
10722 } else {
10723 if(repaired_objp == Player_obj){
10724 p_index = Player_num;
10725 }
10726 }
10727
10728 switch( how ) {
10729 case REPAIR_INFO_BEGIN:
10730 aip->ai_flags.set(AI::AI_Flags::Being_repaired);
10731 aip->ai_flags.remove(AI::AI_Flags::Awaiting_repair);
10732 stamp = timestamp(-1);
10733
10734 // if this is a player ship, then subtract the repair penalty from this player's score
10735 if ( repaired_objp->flags[Object::Object_Flags::Player_ship] ) {
10736 if ( !(Game_mode & GM_MULTIPLAYER) ) {
10737 Player->stats.m_score -= The_mission.ai_profile->repair_penalty[Game_skill_level]; // subtract the penalty
10738 }
10739 }
10740 break;
10741
10742 case REPAIR_INFO_BROKEN:
10743 aip->ai_flags.remove(AI::AI_Flags::Being_repaired);
10744 aip->ai_flags.set(AI::AI_Flags::Awaiting_repair);
10745 stamp = timestamp((int) ((30 + 10*frand()) * 1000));
10746 break;
10747
10748 case REPAIR_INFO_END:
10749 // when only awaiting repair, and the repair is ended, then set support to -1.
10750 if ( aip->ai_flags[AI::AI_Flags::Awaiting_repair] ){
10751 aip->support_ship_objnum = -1;
10752 }
10753 aip->ai_flags.remove(AI::AI_Flags::Being_repaired);
10754 aip->ai_flags.remove(AI::AI_Flags::Awaiting_repair);
10755 stamp = timestamp((int) ((30 + 10*frand()) * 1000));
10756 break;
10757
10758 case REPAIR_INFO_QUEUE:
10759 aip->ai_flags.set(AI::AI_Flags::Awaiting_repair);
10760 if ( aip == Player_ai ){
10761 hud_support_view_start();
10762 }
10763 stamp = timestamp(-1);
10764 break;
10765
10766 case REPAIR_INFO_ABORT:
10767 case REPAIR_INFO_KILLED:
10768 // undock if necessary (we may be just waiting for a repair in which case we aren't docked)
10769 if ((repair_objp != NULL) && dock_check_find_direct_docked_object(repair_objp, repaired_objp))
10770 {
10771 ai_do_objects_undocked_stuff(repair_objp, repaired_objp);
10772 }
10773
10774 // 5/4/98 -- MWA -- Need to set support objnum to -1 to let code know this guy who was getting
10775 // repaired (or queued for repair), isn't really going to be docked with anyone anymore.
10776 aip->support_ship_objnum = -1;
10777 aip->ai_flags.remove(AI::AI_Flags::Being_repaired);
10778 aip->ai_flags.remove(AI::AI_Flags::Awaiting_repair);
10779
10780 if (repair_objp != NULL) {
10781 repair_aip = &Ai_info[Ships[repair_objp->instance].ai_index];
10782 repair_aip->ai_flags.remove(AI::AI_Flags::Being_repaired);
10783 repair_aip->ai_flags.remove(AI::AI_Flags::Awaiting_repair);
10784 }
10785
10786 if ( p_index >= 0 ) {
10787 hud_support_view_abort();
10788
10789 // send appropriate messages here
10790 if (Game_mode & GM_NORMAL || MULTIPLAYER_MASTER) {
10791 if ( how == REPAIR_INFO_KILLED ){
10792 message_send_builtin_to_player( MESSAGE_SUPPORT_KILLED, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_SOON, 0, 0, p_index, p_team );
10793 } else {
10794 if ( repair_objp ){
10795 message_send_builtin_to_player( MESSAGE_REPAIR_ABORTED, &Ships[repair_objp->instance], MESSAGE_PRIORITY_NORMAL, MESSAGE_TIME_SOON, 0, 0, p_index, p_team );
10796 }
10797 }
10798 }
10799 }
10800
10801 // add log entry if this is a player
10802 if ( repaired_objp->flags[Object::Object_Flags::Player_ship] ){
10803 mission_log_add_entry(LOG_PLAYER_ABORTED_REARM, Ships[repaired_objp->instance].ship_name, NULL);
10804 }
10805
10806 stamp = timestamp((int) ((30 + 10*frand()) * 1000));
10807 break;
10808
10809 case REPAIR_INFO_COMPLETE:
10810 // clear the being repaired flag -- and
10811 if ( p_index >= 0 ) {
10812 Assert( repair_objp );
10813
10814 hud_support_view_stop();
10815
10816 if (Game_mode & GM_NORMAL || MULTIPLAYER_MASTER) {
10817 message_send_builtin_to_player(MESSAGE_REPAIR_DONE, &Ships[repair_objp->instance], MESSAGE_PRIORITY_LOW, MESSAGE_TIME_SOON, 0, 0, p_index, p_team);
10818 }
10819 }
10820 stamp = timestamp((int) ((30 + 10*frand()) * 1000));
10821 break;
10822
10823 case REPAIR_INFO_ONWAY:
10824 // need to set the signature so that clients in multiplayer games rearm correctly
10825 Assert( repair_objp );
10826 aip->support_ship_signature = repair_objp->signature;
10827 aip->support_ship_objnum = OBJ_INDEX(repair_objp);
10828 stamp = timestamp(-1);
10829 break;
10830
10831 default:
10832 Int3(); // bogus type of repair info
10833 }
10834
10835 // repair_objp might be NULL is we are cleaning up this mode because of the support ship
10836 // getting killed.
10837 if ( repair_objp ) {
10838 Ai_info[Ships[repair_objp->instance].ai_index].warp_out_timestamp = stamp;
10839 aip = &Ai_info[Ships[repair_objp->instance].ai_index];
10840
10841 switch ( how ) {
10842 case REPAIR_INFO_ONWAY:
10843 Assert( repaired_objp != NULL );
10844 aip->goal_objnum = OBJ_INDEX(repaired_objp);
10845 aip->ai_flags.set(AI::AI_Flags::Repairing);
10846 break;
10847
10848 case REPAIR_INFO_BROKEN:
10849 break;
10850
10851 case REPAIR_INFO_END:
10852 case REPAIR_INFO_ABORT:
10853 case REPAIR_INFO_KILLED:
10854 if ( how == REPAIR_INFO_ABORT )
10855 aip->goal_objnum = -1;
10856
10857 aip->ai_flags.remove(AI::AI_Flags::Repairing);
10858 break;
10859
10860 case REPAIR_INFO_QUEUE:
10861 ai_add_rearm_goal( repaired_objp, repair_objp );
10862 break;
10863
10864 case REPAIR_INFO_BEGIN:
10865 case REPAIR_INFO_COMPLETE:
10866 break;
10867
10868 default:
10869 Int3(); // bogus type of repair info
10870 }
10871 }
10872
10873 multi_maybe_send_repair_info( repaired_objp, repair_objp, how );
10874 }
10875
10876 // Goober5000 - helper function that is also called from ai_dock()
ai_get_dock_goal_indexes(object * objp,ai_info * aip,ai_goal * aigp,object * goal_objp,int & docker_index,int & dockee_index)10877 void ai_get_dock_goal_indexes(object *objp, ai_info *aip, ai_goal *aigp, object *goal_objp, int &docker_index, int &dockee_index)
10878 {
10879 // get the indexes
10880 switch (aip->submode)
10881 {
10882 case AIS_DOCK_1:
10883 case AIS_DOCK_2:
10884 case AIS_DOCK_3:
10885 Warning(LOCATION, "Normally dock indexes should be calculated for only AIS_DOCK_0 and AIS_UNDOCK_0. Trace out and debug.");
10886 FALLTHROUGH;
10887 case AIS_DOCK_0:
10888 {
10889 // get them from the active goal
10890 Assert(aigp != NULL);
10891 Assert(aigp->flags[AI::Goal_Flags::Dockee_index_valid] && aigp->flags[AI::Goal_Flags::Docker_index_valid]);
10892 docker_index = aigp->docker.index;
10893 dockee_index = aigp->dockee.index;
10894 Assert(docker_index >= 0);
10895 Assert(dockee_index >= 0);
10896 break;
10897 }
10898
10899 case AIS_DOCK_4:
10900 case AIS_DOCK_4A:
10901 case AIS_UNDOCK_1:
10902 case AIS_UNDOCK_2:
10903 Warning(LOCATION, "Normally dock indexes should be calculated for only AIS_DOCK_0 and AIS_UNDOCK_0. Trace out and debug.");
10904 FALLTHROUGH;
10905 case AIS_UNDOCK_0:
10906 {
10907 // get them from the guy I'm docked to
10908 Assert(goal_objp != NULL);
10909 docker_index = dock_find_dockpoint_used_by_object(objp, goal_objp);
10910 dockee_index = dock_find_dockpoint_used_by_object(goal_objp, objp);
10911 Assert(docker_index >= 0);
10912 Assert(dockee_index >= 0);
10913 break;
10914 }
10915
10916 case AIS_UNDOCK_3:
10917 case AIS_UNDOCK_4:
10918 {
10919 Warning(LOCATION, "Normally dock indexes should be calculated for only AIS_DOCK_0 and AIS_UNDOCK_0. Additionally, dock indexes can't always be determined for AIS_UNDOCK_3 or AIS_UNDOCK_4. Trace out and debug.");
10920 docker_index = -1;
10921 dockee_index = -1;
10922 break;
10923 }
10924
10925 default:
10926 {
10927 Error(LOCATION, "Unknown docking submode!");
10928 docker_index = -1;
10929 dockee_index = -1;
10930 break;
10931 }
10932 }
10933 }
10934
10935 // Goober5000 - clean up my own dock mode
ai_cleanup_dock_mode_subjective(object * objp)10936 void ai_cleanup_dock_mode_subjective(object *objp)
10937 {
10938 ship *shipp = &Ships[objp->instance];
10939
10940 // get ai of object
10941 ai_info *aip = &Ai_info[shipp->ai_index];
10942
10943 // if the object is in dock mode, force them to near last stage
10944 if ( (aip->mode == AIM_DOCK) && (aip->submode < AIS_UNDOCK_3) )
10945 {
10946 // get the object being acted upon
10947 object *goal_objp;
10948 ship *goal_shipp;
10949 if (aip->goal_objnum >= 0)
10950 {
10951 goal_objp = &Objects[aip->goal_objnum];
10952 Assert(goal_objp->type == OBJ_SHIP);
10953 goal_shipp = &Ships[goal_objp->instance];
10954 }
10955 else
10956 {
10957 goal_objp = NULL;
10958 goal_shipp = NULL;
10959 }
10960
10961 // get the indexes from the saved parameters
10962 int docker_index = aip->submode_parm0;
10963 int dockee_index = aip->submode_parm1;
10964
10965 // undo all the appropriate triggers
10966 switch (aip->submode)
10967 {
10968 case AIS_UNDOCK_0:
10969 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, -1);
10970 model_anim_start_type(goal_shipp, AnimationTriggerType::Docked, dockee_index, -1);
10971 FALLTHROUGH;
10972 case AIS_UNDOCK_1:
10973 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1);
10974 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
10975 FALLTHROUGH;
10976 case AIS_UNDOCK_2:
10977 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, -1);
10978 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1);
10979 break;
10980
10981 case AIS_DOCK_4:
10982 case AIS_DOCK_4A:
10983 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, -1);
10984 model_anim_start_type(goal_shipp, AnimationTriggerType::Docked, dockee_index, -1);
10985 FALLTHROUGH;
10986 case AIS_DOCK_3:
10987 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1);
10988 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
10989 FALLTHROUGH;
10990 case AIS_DOCK_2:
10991 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, -1);
10992 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1);
10993 break;
10994 }
10995
10996 aip->submode = AIS_UNDOCK_3;
10997 aip->submode_start_time = Missiontime;
10998 }
10999 }
11000
11001 // Goober5000 - clean up the dock mode of everybody around me
11002 // (This function should ONLY need to be called from a ship doing a deathroll.
11003 // It ensures that any ship docking or undocking with it will finish gracefully.)
ai_cleanup_dock_mode_objective(object * objp)11004 void ai_cleanup_dock_mode_objective(object *objp)
11005 {
11006 // process all directly docked objects
11007 for (dock_instance *ptr = objp->dock_list; ptr != NULL; ptr = ptr->next)
11008 ai_cleanup_dock_mode_subjective(ptr->docked_objp);
11009 }
11010
11011 // Goober5000
11012 // (This function should ONLY need to be called from a ship doing a deathroll.
11013 // It ensures that any support ship stuff will be wrapped up gracefully.)
ai_cleanup_rearm_mode(object * objp)11014 void ai_cleanup_rearm_mode(object *objp)
11015 {
11016 ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
11017
11018 if (aip->ai_flags[AI::AI_Flags::Repairing]) {
11019 Assert( aip->goal_objnum != -1 );
11020 ai_do_objects_repairing_stuff( &Objects[aip->goal_objnum], objp, REPAIR_INFO_KILLED );
11021 } else if ( aip->ai_flags[AI::AI_Flags::Being_repaired] ) {
11022 // MWA/Goober5000 -- note that we have to use support object here instead of goal_objnum.
11023 Assert( aip->support_ship_objnum != -1 );
11024 ai_do_objects_repairing_stuff( objp, &Objects[aip->support_ship_objnum], REPAIR_INFO_ABORT );
11025 } else if ( aip->ai_flags[AI::AI_Flags::Awaiting_repair] ) {
11026 // MWA/Goober5000 -- note that we have to use support object here instead of goal_objnum.
11027 // MWA -- 3/38/98 Check to see if this guy is queued for a support ship, or there is already
11028 // one in the mission
11029 if ( mission_is_repair_scheduled(objp) ) {
11030 mission_remove_scheduled_repair( objp ); // this function will notify multiplayer clients.
11031 } else {
11032 if ( aip->support_ship_objnum != -1 )
11033 ai_do_objects_repairing_stuff( objp, &Objects[aip->support_ship_objnum], REPAIR_INFO_ABORT );
11034 else
11035 ai_do_objects_repairing_stuff( objp, NULL, REPAIR_INFO_ABORT );
11036 }
11037 }
11038 }
11039
11040 // Make Pl_objp point at aip->goal_point.
ai_still()11041 void ai_still()
11042 {
11043 ship *shipp;
11044 ai_info *aip;
11045
11046 Assert(Pl_objp->type == OBJ_SHIP);
11047 Assert((Pl_objp->instance >= 0) && (Pl_objp->instance < MAX_OBJECTS));
11048
11049 shipp = &Ships[Pl_objp->instance];
11050 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
11051
11052 aip = &Ai_info[shipp->ai_index];
11053
11054 turn_towards_point(Pl_objp, &aip->goal_point, NULL, 0.0f);
11055 }
11056
11057 // Make *Pl_objp stay near another ship.
ai_stay_near()11058 void ai_stay_near()
11059 {
11060 ai_info *aip;
11061 int goal_objnum;
11062
11063 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
11064
11065 goal_objnum = aip->goal_objnum;
11066
11067 if ((goal_objnum < 0) || (Objects[goal_objnum].type != OBJ_SHIP) || (Objects[goal_objnum].signature != aip->goal_signature)) {
11068 aip->mode = AIM_NONE;
11069 } else {
11070 float dist, max_dist, scale;
11071 vec3d rand_vec, goal_pos, vec_to_goal;
11072 object *goal_objp;
11073
11074 goal_objp = &Objects[goal_objnum];
11075
11076 // Make not all ships pursue same point.
11077 static_randvec(OBJ_INDEX(Pl_objp), &rand_vec);
11078
11079 // Make sure point is in front hemisphere (relative to Pl_objp's position.
11080 vm_vec_sub(&vec_to_goal, &goal_objp->pos, &Pl_objp->pos);
11081 if (vm_vec_dot(&rand_vec, &vec_to_goal) > 1.0f) {
11082 vm_vec_negate(&rand_vec);
11083 }
11084
11085 // Scale the random vector by an amount proportional to the distance from Pl_objp to the true goal.
11086 dist = vm_vec_dist_quick(&goal_objp->pos, &Pl_objp->pos);
11087 max_dist = aip->stay_near_distance;
11088 scale = dist - max_dist/2;
11089 if (scale < 0.0f)
11090 scale = 0.0f;
11091
11092 vm_vec_scale_add(&goal_pos, &goal_objp->pos, &rand_vec, scale);
11093
11094 if (max_dist < Pl_objp->radius + goal_objp->radius + 25.0f)
11095 max_dist = Pl_objp->radius + goal_objp->radius + 25.0f;
11096
11097 if (dist > max_dist) {
11098 turn_towards_point(Pl_objp, &goal_pos, NULL, 0.0f);
11099 accelerate_ship(aip, dist / max_dist - 0.8f);
11100 }
11101
11102 }
11103
11104 }
11105
11106 // Warn player if dock path is obstructed.
maybe_dock_obstructed(object * cur_objp,object * goal_objp,int big_only_flag)11107 int maybe_dock_obstructed(object *cur_objp, object *goal_objp, int big_only_flag)
11108 {
11109 vec3d *goalpos, *curpos;
11110 float radius;
11111 ai_info *aip;
11112 int collide_objnum;
11113
11114 aip = &Ai_info[Ships[cur_objp->instance].ai_index];
11115
11116 Ai_info[Ships[goal_objp->instance].ai_index].ai_flags.remove(AI::AI_Flags::Repair_obstructed);
11117
11118 if (goal_objp != Player_obj)
11119 return -1;
11120
11121 curpos = &cur_objp->pos;
11122 radius = cur_objp->radius;
11123 goalpos = &Path_points[aip->path_cur].pos;
11124 collide_objnum = pp_collide_any(curpos, goalpos, radius, cur_objp, goal_objp, big_only_flag);
11125
11126 if (collide_objnum != -1)
11127 Ai_info[Ships[goal_objp->instance].ai_index].ai_flags.set(AI::AI_Flags::Repair_obstructed);
11128
11129 return collide_objnum;
11130 }
11131
11132
11133 // Docking behavior.
11134 // Approach a ship, follow path to docking platform, approach platform; after awhile,
11135 // undock.
ai_dock()11136 void ai_dock()
11137 {
11138 ship *shipp = &Ships[Pl_objp->instance];
11139 ai_info *aip = &Ai_info[shipp->ai_index];
11140
11141 // Make sure we still have a dock goal.
11142 // Make sure the object we're supposed to dock with or undock from still exists.
11143 if ( ((aip->active_goal < 0) && (aip->submode != AIS_DOCK_4A))
11144 || (aip->goal_objnum < 0)
11145 || (Objects[aip->goal_objnum].signature != aip->goal_signature) )
11146 {
11147 ai_cleanup_dock_mode_subjective(Pl_objp);
11148 }
11149
11150 ship_info *sip = &Ship_info[shipp->ship_info_index];
11151
11152 // we need to keep the dock indexes stored in the submode because the goal may become invalid at any point
11153 // (when we first dock or first undock, we'll calculate and overwrite these for the first time)
11154 int docker_index = aip->submode_parm0;
11155 int dockee_index = aip->submode_parm1;
11156
11157 // get the active goal
11158 ai_goal *aigp;
11159 if (aip->active_goal >= 0)
11160 aigp = &aip->goals[aip->active_goal];
11161 else
11162 aigp = NULL;
11163
11164 // get the object being acted upon
11165 object *goal_objp;
11166 ship *goal_shipp;
11167 if (aip->goal_objnum >= 0)
11168 {
11169 goal_objp = &Objects[aip->goal_objnum];
11170 Assert(goal_objp->type == OBJ_SHIP);
11171 goal_shipp = &Ships[goal_objp->instance];
11172 }
11173 else
11174 {
11175 goal_objp = NULL;
11176 goal_shipp = NULL;
11177 }
11178
11179
11180 // For docking submodes (ie, not undocking), follow path. Once at second last
11181 // point on path (point just before point on dock platform), orient into position.
11182 //
11183 // For undocking, first mode pushes docked ship straight back from docking point
11184 // second mode turns ship and moves to point on docking radius
11185 switch (aip->submode)
11186 {
11187
11188 // This mode means to find the path to the docking point.
11189 case AIS_DOCK_0:
11190 {
11191 // save the dock indexes we're currently using
11192 ai_get_dock_goal_indexes(Pl_objp, aip, aigp, goal_objp, docker_index, dockee_index);
11193 aip->submode_parm0 = docker_index;
11194 aip->submode_parm1 = dockee_index;
11195
11196 ai_path();
11197 if (aip->path_length < 4)
11198 {
11199 Assert(goal_objp != NULL);
11200 ship_info *goal_sip = &Ship_info[goal_shipp->ship_info_index];
11201 char *goal_ship_class_name = goal_sip->name;
11202 char *goal_dock_path_name = model_get(goal_sip->model_num)->paths[aip->mp_index].name;
11203
11204 mprintf(("Ship class %s has only %i points on dock path \"%s\". Recommended minimum number of points is 4. "\
11205 "Docking along that path will look strange. You may wish to edit the model.\n", goal_ship_class_name, aip->path_length, goal_dock_path_name));
11206 }
11207
11208 aip->submode = AIS_DOCK_1;
11209 aip->submode_start_time = Missiontime;
11210 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage1, docker_index, 1);
11211 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, 1);
11212
11213 aip->path_start = -1;
11214 break;
11215 }
11216
11217 // This mode means to follow the path until just before the end.
11218 case AIS_DOCK_1:
11219 {
11220 int r;
11221
11222 if ((r = maybe_dock_obstructed(Pl_objp, goal_objp, 1)) != -1) {
11223 int r1;
11224 if ((r1 = maybe_avoid_big_ship(Pl_objp, goal_objp, aip, &goal_objp->pos, 7.0f)) != 0) {
11225 nprintf(("AI", "Support ship %s avoiding large ship %s\n", Ships[Pl_objp->instance].ship_name, Ships[Objects[r1].instance].ship_name));
11226 break;
11227 }
11228 } //else {
11229 {
11230 ai_path();
11231
11232 if (aip->path_cur-aip->path_start >= aip->path_length-1) { // If got this far, advance no matter what.
11233 aip->submode = AIS_DOCK_2;
11234 aip->submode_start_time = Missiontime;
11235 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, 1);
11236 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, 1);
11237
11238 aip->path_cur--;
11239 Assert(aip->path_cur-aip->path_start >= 0);
11240 } else if (aip->path_cur-aip->path_start >= aip->path_length-2) {
11241 if (Pl_objp->phys_info.speed > goal_objp->phys_info.speed + 1.5f) {
11242 set_accel_for_target_speed(Pl_objp, goal_objp->phys_info.speed);
11243 } else {
11244 aip->submode = AIS_DOCK_2;
11245 aip->submode_start_time = Missiontime;
11246 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, 1);
11247 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, 1);
11248 }
11249 }
11250 }
11251 break;
11252 }
11253
11254 // This mode means to drag oneself right to the second last point on the path.
11255 // Path code allows it to overshoot.
11256 case AIS_DOCK_2:
11257 {
11258 float dist;
11259 int r;
11260
11261 if ((r = maybe_dock_obstructed(Pl_objp, goal_objp, 0)) != -1) {
11262 nprintf(("AI", "Dock 2: Obstructed by %s\n", Ships[Objects[r].instance].ship_name));
11263 accelerate_ship(aip, 0.0f);
11264
11265 aip->submode = AIS_DOCK_1;
11266 aip->submode_start_time = Missiontime;
11267 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, -1);
11268 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1);
11269 } else {
11270 dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_APPROACH);
11271 Assert(dist != UNINITIALIZED_VALUE);
11272
11273 float tolerance;
11274 if (goal_objp->flags[Object::Object_Flags::Player_ship])
11275 tolerance = 6*flFrametime + 1.0f;
11276 else
11277 tolerance = 4*flFrametime + 0.5f;
11278
11279 if ( dist < tolerance) {
11280 aip->submode = AIS_DOCK_3;
11281 aip->submode_start_time = Missiontime;
11282 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, 1);
11283 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, 1);
11284
11285 aip->path_cur++;
11286 }
11287 }
11288 break;
11289 }
11290
11291 case AIS_DOCK_3:
11292 {
11293 Assert(aip->goal_objnum != -1);
11294 int r;
11295
11296 if ((r = maybe_dock_obstructed(Pl_objp, goal_objp,0)) != -1) {
11297 nprintf(("AI", "Dock 1: Obstructed by %s\n", Ships[Objects[r].instance].ship_name));
11298 accelerate_ship(aip, 0.0f);
11299
11300 aip->submode = AIS_DOCK_2;
11301 aip->submode_start_time = Missiontime;
11302 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1);
11303 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
11304 } else {
11305 rotating_dockpoint_info rdinfo;
11306
11307 float dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_DOCK, &rdinfo);
11308 Assert(dist != UNINITIALIZED_VALUE);
11309
11310 float tolerance = 2*flFrametime * (1.0f + fl_sqrt(goal_objp->phys_info.speed));
11311
11312 // Goober5000
11313 if (rdinfo.submodel >= 0)
11314 {
11315 tolerance += 4*flFrametime * (rdinfo.submodel_r * rdinfo.submodel_w);
11316 }
11317
11318 if (dist < tolerance)
11319 {
11320 // - Removed by MK on 11/7/97, causes errors for ships docked at mission start: maybe_recreate_path(Pl_objp, aip, 1);
11321 dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_DOCK);
11322 Assert(dist != UNINITIALIZED_VALUE);
11323
11324 physics_ship_init(Pl_objp);
11325
11326 ai_do_objects_docked_stuff( Pl_objp, docker_index, goal_objp, dockee_index );
11327
11328 if (aip->submode == AIS_DOCK_3) {
11329 // Play a ship docking attach sound
11330 snd_play_3d( gamesnd_get_game_sound(GameSounds::DOCK_ATTACH), &Pl_objp->pos, &View_position );
11331
11332 // start the dock animation
11333 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, 1);
11334 model_anim_start_type(goal_shipp, AnimationTriggerType::Docked, dockee_index, 1);
11335
11336 if ((Pl_objp == Player_obj) || (goal_objp == Player_obj))
11337 joy_ff_docked(); // shake player's joystick a little
11338 }
11339
11340 // If this ship is repairing another ship...
11341 if (aip->ai_flags[AI::AI_Flags::Repairing]) {
11342 aip->submode = AIS_DOCK_4; // Special rearming only dock mode.
11343 aip->submode_start_time = Missiontime;
11344 } else {
11345 aip->submode = AIS_DOCK_4A;
11346 aip->submode_start_time = Missiontime;
11347 }
11348 }
11349 }
11350 break;
11351 }
11352
11353 // Yes, we just sit here. We wait for further orders. No, it's not a bug.
11354 case AIS_DOCK_4A:
11355 {
11356 if (aigp == NULL) { // Can happen for initially docked ships.
11357 // this now "just sits here" for docked ships
11358 ai_do_default_behavior( &Objects[Ships[aip->shipnum].objnum] ); // do the default behavior
11359 } else {
11360 mission_log_add_entry(LOG_SHIP_DOCKED, shipp->ship_name, goal_shipp->ship_name);
11361
11362 if (aigp->ai_mode == AI_GOAL_DOCK) {
11363 ai_mission_goal_complete( aip ); // Note, this calls ai_do_default_behavior().
11364 }
11365 }
11366
11367 break;
11368 }
11369
11370 case AIS_DOCK_4:
11371 {
11372 // This mode is only for rearming/repairing.
11373 // The ship that is performing the rearm enters this mode after it docks.
11374 Assert((aip->goal_objnum >= -1) && (aip->goal_objnum < MAX_OBJECTS));
11375
11376 float dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_DOCK);
11377 Assert(dist != UNINITIALIZED_VALUE);
11378
11379 ai_info *goal_aip = &Ai_info[goal_shipp->ai_index];
11380
11381 // Goober5000 - moved from call_doa
11382 // Abort if the ship being repaired exceeds my max speed
11383 if (goal_objp->phys_info.speed > MAX_REPAIR_SPEED)
11384 {
11385 // call the ai_abort rearm request code
11386 ai_abort_rearm_request( Pl_objp );
11387 // Goober5000 - add missionlog for support ships, per Mantis #2999
11388 mission_log_add_entry(LOG_SHIP_UNDOCKED, shipp->ship_name, goal_shipp->ship_name);
11389 }
11390 // Make sure repair has not broken off.
11391 else if (dist > 5.0f) // Oops, too far away!
11392 {
11393 if ( goal_aip->ai_flags[AI::AI_Flags::Being_repaired] ) {
11394 ai_do_objects_repairing_stuff( goal_objp, Pl_objp, REPAIR_INFO_BROKEN);
11395 // Goober5000 - add missionlog for support ships, per Mantis #2999
11396 mission_log_add_entry(LOG_SHIP_UNDOCKED, shipp->ship_name, goal_shipp->ship_name);
11397 }
11398
11399 if (dist > Pl_objp->radius*2 + goal_objp->radius*2) {
11400 // Got real far away from goal, so move back a couple modes and try again.
11401 aip->submode = AIS_DOCK_2;
11402 aip->submode_start_time = Missiontime;
11403 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, -1);
11404 model_anim_start_type(goal_shipp, AnimationTriggerType::Docked, dockee_index, -1);
11405 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1);
11406 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
11407 }
11408 }
11409 else
11410 {
11411 if ( goal_aip->ai_flags[AI::AI_Flags::Awaiting_repair] ) {
11412 ai_do_objects_repairing_stuff( goal_objp, Pl_objp, REPAIR_INFO_BEGIN );
11413 // Goober5000 - add missionlog for support ships, per Mantis #2999
11414 mission_log_add_entry(LOG_SHIP_DOCKED, shipp->ship_name, goal_shipp->ship_name);
11415 }
11416 }
11417
11418 break;
11419 }
11420
11421 case AIS_UNDOCK_0:
11422 {
11423 // First stage of undocking.
11424 int path_num;
11425
11426 // If this is the first frame for this submode, play the animation and set the timestamp
11427 if (aip->mode_time < 0)
11428 {
11429 // save the dock indexes we're currently using
11430 ai_get_dock_goal_indexes(Pl_objp, aip, aigp, goal_objp, docker_index, dockee_index);
11431 aip->submode_parm0 = docker_index;
11432 aip->submode_parm1 = dockee_index;
11433
11434 // start the detach animation (opposite of the dock animation)
11435 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, -1);
11436 model_anim_start_type(goal_shipp, AnimationTriggerType::Docked, dockee_index, -1);
11437
11438 // calculate time until animations elapse
11439 int time1 = model_anim_get_time_type(shipp, AnimationTriggerType::Docked, docker_index);
11440 int time2 = model_anim_get_time_type(goal_shipp, AnimationTriggerType::Docked, dockee_index);
11441 aip->mode_time = timestamp(MAX(time1, time2));
11442 }
11443
11444 // if not enough time has passed, just wait
11445 if (!timestamp_elapsed(aip->mode_time))
11446 break;
11447
11448 // clear timestamp
11449 aip->mode_time = -1;
11450
11451 // set up the path points for the undocking procedure
11452 path_num = ai_return_path_num_from_dockbay(goal_objp, dockee_index);
11453 Assert(path_num >= 0);
11454 ai_find_path(Pl_objp, OBJ_INDEX(goal_objp), path_num, 0);
11455
11456 // Play a ship docking detach sound
11457 snd_play_3d( gamesnd_get_game_sound(GameSounds::DOCK_DETACH), &Pl_objp->pos, &View_position );
11458
11459 aip->submode = AIS_UNDOCK_1;
11460 aip->submode_start_time = Missiontime;
11461 break;
11462 }
11463
11464 case AIS_UNDOCK_1:
11465 {
11466 // Using thrusters, exit from dock station to nearest next dock path point.
11467 float dist;
11468 rotating_dockpoint_info rdinfo;
11469
11470 // Waiting for one second to elapse to let detach sound effect play out.
11471 if (Missiontime - aip->submode_start_time < REARM_BREAKOFF_DELAY)
11472 break;
11473
11474 // play the depart sound, but only once, since this mode is called multiple times per frame
11475 if ( !(aigp->flags[AI::Goal_Flags::Depart_sound_played]))
11476 {
11477 snd_play_3d( gamesnd_get_game_sound(GameSounds::DOCK_DEPART), &Pl_objp->pos, &View_position );
11478 aigp->flags.set(AI::Goal_Flags::Depart_sound_played);
11479 }
11480
11481 dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_UNDOCK_1, &rdinfo);
11482 Assert(dist != UNINITIALIZED_VALUE);
11483
11484 float dist_to_dock;
11485
11486 // Goober5000 - if via submodel, calc distance to point, not center
11487 if (rdinfo.submodel >= 0)
11488 dist_to_dock = vm_vec_dist_quick(&Pl_objp->pos, &rdinfo.dockee_point);
11489 else
11490 dist_to_dock = vm_vec_dist_quick(&Pl_objp->pos, &goal_objp->pos);
11491
11492 // Move to within 0.1 units of second last point on path before orienting, or just plain far away from docked-to ship.
11493 // This allows undock to complete if first ship flies away.
11494 if ((dist < 2*flFrametime) || (dist_to_dock > 2*Pl_objp->radius)) {
11495 aip->submode = AIS_UNDOCK_2;
11496 aip->submode_start_time = Missiontime;
11497 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1);
11498 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
11499 }
11500 break;
11501 }
11502
11503 case AIS_UNDOCK_2:
11504 {
11505 float dist;
11506
11507 // get pointer to docked object's aip to reset flags, etc
11508 Assert( aip->goal_objnum != -1 );
11509
11510 // Second stage of undocking.
11511 dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_UNDOCK_2);
11512 Assert(dist != UNINITIALIZED_VALUE);
11513
11514 // If at goal point, or quite far away from dock object
11515 // NOTE: the speed check has an etra 5 thousandths added on to account for some floating point error
11516 if ((dist < 2.0f) || (vm_vec_dist_quick(&Pl_objp->pos, &goal_objp->pos) > (Pl_objp->radius + goal_objp->radius)*2) || ((goal_objp->phys_info.speed + 0.005f) > MAX_UNDOCK_ABORT_SPEED) ) {
11517 // reset the dock flags. If rearm/repair, reset rearm repair flags for those ships as well.
11518 if ( sip->flags[Ship::Info_Flags::Support] ) {
11519 ai_do_objects_repairing_stuff( &Objects[aip->support_ship_objnum], Pl_objp, REPAIR_INFO_END );
11520 }
11521
11522 // clear out dock stuff for both objects.
11523 ai_do_objects_undocked_stuff( Pl_objp, goal_objp );
11524 physics_ship_init(Pl_objp);
11525
11526 aip->submode = AIS_UNDOCK_3;
11527 aip->submode_start_time = Missiontime;
11528 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, -1);
11529 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1);
11530
11531 // don't add undock log entries for support ships.
11532 // Goober5000 - deliberately contravening Volition's above comment - add missionlog for support ships, per Mantis #2999
11533 mission_log_add_entry(LOG_SHIP_UNDOCKED, shipp->ship_name, goal_shipp->ship_name);
11534 }
11535 break;
11536 }
11537
11538 case AIS_UNDOCK_3:
11539 {
11540 if (goal_objp == NULL)
11541 {
11542 // this might happen when a goal is cancelled before docking has finished
11543 aip->submode = AIS_UNDOCK_4;
11544 aip->submode_start_time = Missiontime;
11545 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage1, docker_index, -1);
11546 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, -1);
11547 }
11548 else
11549 {
11550 float dist = dock_orient_and_approach(Pl_objp, docker_index, goal_objp, dockee_index, DOA_UNDOCK_3);
11551 Assert(dist != UNINITIALIZED_VALUE);
11552
11553 if (dist < Pl_objp->radius/2 + 5.0f) {
11554 aip->submode = AIS_UNDOCK_4;
11555 aip->submode_start_time = Missiontime;
11556 model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage1, docker_index, -1);
11557 model_anim_start_type(goal_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, -1);
11558 }
11559
11560 // possible that this flag hasn't been cleared yet. When aborting a rearm, this submode might
11561 // be entered directly.
11562 if ( (sip->flags[Ship::Info_Flags::Support]) && (aip->ai_flags[AI::AI_Flags::Repairing]) ) {
11563 ai_do_objects_repairing_stuff( goal_objp, Pl_objp, REPAIR_INFO_ABORT );
11564 }
11565 }
11566
11567 break;
11568 }
11569
11570 case AIS_UNDOCK_4:
11571 {
11572
11573 aip->mode = AIM_NONE;
11574
11575 // only call mission goal complete if this was indeed an undock goal
11576 if ( aip->active_goal >= 0 ) {
11577 if ( aigp->ai_mode == AI_GOAL_UNDOCK )
11578 ai_mission_goal_complete( aip ); // this call should reset the AI mode
11579 }
11580
11581 break;
11582 }
11583
11584 default:
11585 {
11586 Int3(); // Error, bogus submode
11587 }
11588
11589 } // end of switch statement
11590 }
11591
11592 #ifndef NDEBUG
11593
11594 #define MAX_AI_DEBUG_RENDER_STUFF 100
11595 typedef struct ai_render_stuff {
11596 ship_subsys *ss;
11597 int parent_objnum;
11598 } ai_render_stuff;
11599
11600 ai_render_stuff AI_debug_render_stuff[MAX_AI_DEBUG_RENDER_STUFF];
11601
11602 int Num_AI_debug_render_stuff = 0;
11603
ai_debug_render_stuff()11604 void ai_debug_render_stuff()
11605 {
11606
11607 vertex vert1, vert2;
11608 vec3d gpos2;
11609 int i;
11610
11611 for (i=0; i<Num_AI_debug_render_stuff; i++) {
11612 ship_subsys *ss;
11613 int parent_objnum;
11614 vec3d gpos, gvec;
11615 model_subsystem *tp;
11616
11617 ss = AI_debug_render_stuff[i].ss;
11618 tp = ss->system_info;
11619
11620 parent_objnum = AI_debug_render_stuff[i].parent_objnum;
11621
11622 ship_get_global_turret_info(&Objects[parent_objnum], tp, &gpos, &gvec);
11623 g3_rotate_vertex(&vert1, &gpos);
11624 vm_vec_scale_add(&gpos2, &gpos, &gvec, 20.0f);
11625 g3_rotate_vertex(&vert2, &gpos2);
11626 gr_set_color(0, 0, 255);
11627 g3_draw_sphere(&vert1, 2.0f);
11628 gr_set_color(255, 0, 255);
11629 g3_draw_sphere(&vert2, 2.0f);
11630 g3_draw_line(&vert1, &vert2);
11631 }
11632
11633 Num_AI_debug_render_stuff = 0;
11634 }
11635
11636 #endif
11637
11638
11639
11640 // --------------------------------------------------------------------------
11641 // Process subobjects of object objnum.
11642 // Deal with engines disabled.
process_subobjects(int objnum)11643 void process_subobjects(int objnum)
11644 {
11645 ship_subsys *pss;
11646 object *objp = &Objects[objnum];
11647 ship *shipp = &Ships[objp->instance];
11648 ai_info *aip = &Ai_info[shipp->ai_index];
11649 ship_info *sip = &Ship_info[shipp->ship_info_index];
11650
11651 model_subsystem *psub;
11652 for ( pss = GET_FIRST(&shipp->subsys_list); pss !=END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
11653 psub = pss->system_info;
11654
11655 // Don't process destroyed objects (but allow subobjects with hitpoints disabled -nuke) (but also process subobjects that are allowed to rotate)
11656 if (pss->max_hits > 0 && pss->current_hits <= 0.0f && !(psub->flags[Model::Subsystem_Flags::Destroyed_rotation]))
11657 continue;
11658
11659 switch (psub->type) {
11660 case SUBSYSTEM_TURRET:
11661 // handle ending animations
11662 if ( (pss->turret_animation_position == MA_POS_READY) && timestamp_elapsed(pss->turret_animation_done_time) ) {
11663 if ( model_anim_start_type(shipp, AnimationTriggerType::TurretFiring, pss->system_info->subobj_num, -1) ) {
11664 pss->turret_animation_position = MA_POS_NOT_SET;
11665 }
11666 }
11667
11668 if ( psub->turret_num_firing_points > 0 )
11669 {
11670 ai_turret_execute_behavior(shipp, pss);
11671 } else {
11672 Warning( LOCATION, "Turret %s on ship %s has no firing points assigned to it.\nThis needs to be fixed in the model.\n", psub->name, shipp->ship_name );
11673 }
11674 break;
11675
11676 case SUBSYSTEM_ENGINE:
11677 case SUBSYSTEM_NAVIGATION:
11678 case SUBSYSTEM_COMMUNICATION:
11679 case SUBSYSTEM_WEAPONS:
11680 case SUBSYSTEM_SENSORS:
11681 case SUBSYSTEM_UNKNOWN:
11682 break;
11683
11684 // next set of subsystems may rotation
11685 case SUBSYSTEM_RADAR:
11686 case SUBSYSTEM_SOLAR:
11687 case SUBSYSTEM_GAS_COLLECT:
11688 case SUBSYSTEM_ACTIVATION:
11689 break;
11690 default:
11691 Error(LOCATION, "Illegal subsystem type.\n");
11692 }
11693
11694 // do solar/radar/gas/activator rotation here
11695 ship_do_submodel_rotation(shipp, psub, pss);
11696 }
11697
11698 if (!(Game_mode & GM_LAB)) {
11699 // Deal with a ship with blown out engines.
11700 if (ship_get_subsystem_strength(shipp, SUBSYSTEM_ENGINE) == 0.0f) {
11701 // Karajorma - if Player_use_ai is ever fixed to work on multiplayer it should be checked that any player ships
11702 // aren't under AI control here
11703 if ((!(objp->flags[Object::Object_Flags::Player_ship])) && (sip->is_fighter_bomber()) && !(shipp->flags[Ship::Ship_Flags::Dying])) {
11704 // Goober5000 - don't do anything if docked
11705 if (!object_is_docked(objp)) {
11706 // AL: Only attack forever if not trying to depart to a docking bay. Need to have this in, since
11707 // a ship may get repaired... and it should still try to depart. Since docking bay departures
11708 // are not handled as goals, we don't want to leave the AIM_BAY_DEPART mode.
11709 if (aip->mode != AIM_BAY_DEPART) {
11710 ai_attack_object(objp, nullptr); // Regardless of current mode, enter attack mode.
11711 aip->submode = SM_ATTACK_FOREVER; // Never leave attack submode, don't avoid, evade, etc.
11712 aip->submode_start_time = Missiontime;
11713 }
11714 }
11715 }
11716 }
11717 }
11718 }
11719
11720 // Given an object and the wing it's in, return its index in the wing list.
11721 // This defines its location in the wing formation.
11722 // If the object can't be found in the wing, return -1.
11723 // *objp object of interest
11724 // wingnum the wing *objp is in
get_wing_index(object * objp,int wingnum)11725 int get_wing_index(object *objp, int wingnum)
11726 {
11727 wing *wingp;
11728 int i;
11729
11730 Assert((wingnum >= 0) && (wingnum < MAX_WINGS));
11731
11732 wingp = &Wings[wingnum];
11733
11734 for (i=wingp->current_count-1; i>=0; i--)
11735 if ( objp->instance == wingp->ship_index[i] )
11736 break;
11737
11738 return i; // Note, returns -1 if string not found.
11739 }
11740
11741 // Given a wing, return a pointer to the object of its leader.
11742 // Asserts if object not found.
11743 // Currently, the wing leader is defined as the first object in the wing.
11744 // wingnum Wing number in Wings array.
11745 // If wing leader is disabled, swap it with another ship.
get_wing_leader(int wingnum)11746 object *get_wing_leader(int wingnum)
11747 {
11748 wing *wingp;
11749 int ship_num = 0;
11750
11751 if (wingnum < 0) {
11752 return NULL;
11753 }
11754
11755 Assert( wingnum < MAX_WINGS );
11756
11757 wingp = &Wings[wingnum];
11758
11759 Assert(wingp->current_count != 0); // Make sure there is a leader
11760
11761 ship_num = wingp->ship_index[0];
11762
11763 // If this ship is disabled, try another ship in the wing.
11764 int n = 0;
11765 while (ship_get_subsystem_strength(&Ships[ship_num], SUBSYSTEM_ENGINE) == 0.0f) {
11766 n++;
11767
11768 if (n >= wingp->current_count) {
11769 break;
11770 }
11771
11772 ship_num = wingp->ship_index[n];
11773 }
11774
11775 if ( (n != 0) && (n != wingp->current_count) ) {
11776 int t = wingp->ship_index[0];
11777 wingp->ship_index[0] = wingp->ship_index[n];
11778 wingp->ship_index[n] = t;
11779 }
11780
11781 return &Objects[Ships[ship_num].objnum];
11782 }
11783
11784 #define DEFAULT_WING_X_DELTA 1.0f
11785 #define DEFAULT_WING_Y_DELTA 0.25f
11786 #define DEFAULT_WING_Z_DELTA 0.75f
11787 #define DEFAULT_WING_MAG (fl_sqrt(DEFAULT_WING_X_DELTA*DEFAULT_WING_X_DELTA + DEFAULT_WING_Y_DELTA*DEFAULT_WING_Y_DELTA + DEFAULT_WING_Z_DELTA*DEFAULT_WING_Z_DELTA))
11788 // next constant is higher that MAX_SHIPS_IN_WINGS to deal with forming on player's wing
11789 #define MAX_FORMATION_ROWS 4
11790
11791 // Given a position in a wing, return the desired location of the ship relative to the leader
11792 // *_delta_vec OUTPUT. delta vector based on wing_index
11793 // wing_index position in wing.
get_wing_delta(vec3d * _delta_vec,int wing_index)11794 void get_wing_delta(vec3d *_delta_vec, int wing_index)
11795 {
11796 int wi0;
11797
11798 Assert(wing_index >= 0);
11799
11800 int k, row, column;
11801
11802 int bank = wing_index / (MAX_FORMATION_ROWS*(MAX_FORMATION_ROWS+1)/2);
11803 wi0 = wing_index % (MAX_FORMATION_ROWS * (MAX_FORMATION_ROWS+1)/2);
11804
11805 k = 0;
11806 for (row=1; row<MAX_FORMATION_ROWS+1; row++) {
11807 k += row;
11808 if (wi0 < k)
11809 break;
11810 }
11811
11812 row--;
11813 column = wi0 - k + row + 1;
11814
11815 _delta_vec->xyz.x = ((float) column - (float) row/2.0f) * DEFAULT_WING_X_DELTA/DEFAULT_WING_MAG;
11816 _delta_vec->xyz.y = ((float)row + (float)bank*2.25f) * DEFAULT_WING_Y_DELTA/DEFAULT_WING_MAG;
11817 _delta_vec->xyz.z = - ((float)row + 0.5f * (float) bank) * DEFAULT_WING_Z_DELTA/DEFAULT_WING_MAG;
11818
11819 }
11820
11821 /**
11822 * Compute the largest radius of a ship in a *objp's wing.
11823 */
gwlr_1(object * objp,ai_info * aip)11824 float gwlr_1(object *objp, ai_info *aip)
11825 {
11826 int wingnum = aip->wing;
11827 float max_radius;
11828 object *o;
11829 ship_obj *so;
11830
11831 Assert(wingnum >= 0);
11832
11833 max_radius = objp->radius;
11834
11835 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
11836 o = &Objects[so->objnum];
11837 if (Ai_info[Ships[o->instance].ai_index].wing == wingnum)
11838 if (o->radius > max_radius)
11839 max_radius = o->radius;
11840 }
11841
11842 return max_radius;
11843 }
11844
11845 /**
11846 * Compute the largest radius of a ship forming on *objp's wing.
11847 */
gwlr_object_1(object * objp)11848 static float gwlr_object_1(object *objp)
11849 {
11850 float max_radius;
11851 object *o;
11852 ship_obj *so;
11853
11854 max_radius = objp->radius;
11855
11856 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
11857 o = &Objects[so->objnum];
11858 if (Ai_info[Ships[o->instance].ai_index].goal_objnum == OBJ_INDEX(objp))
11859 if (o->radius > max_radius)
11860 max_radius = o->radius;
11861 }
11862
11863 return max_radius;
11864 }
11865
11866 /**
11867 * For the wing that *objp is part of, return the largest ship radius in that wing.
11868 */
get_wing_largest_radius(object * objp,int formation_object_flag)11869 float get_wing_largest_radius(object *objp, int formation_object_flag)
11870 {
11871 ship *shipp;
11872 ai_info *aip;
11873
11874 Assert(objp->type == OBJ_SHIP);
11875 Assert((objp->instance >= 0) && (objp->instance < MAX_OBJECTS));
11876 shipp = &Ships[objp->instance];
11877 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
11878 aip = &Ai_info[shipp->ai_index];
11879
11880 if (formation_object_flag) {
11881 return gwlr_object_1(objp);
11882 } else {
11883 return gwlr_1(objp, aip);
11884 }
11885
11886 }
11887
11888 float Wing_y_scale = 2.0f;
11889 float Wing_scale = 1.0f;
11890 DCF(wing_y_scale, "Adjusts the wing formation scale along the Y axis (Default is 2.0)")
11891 {
11892 dc_stuff_float(&Wing_y_scale);
11893 }
11894
11895 DCF(wing_scale, "Adjusts the wing formation scale. (Default is 1.0f)")
11896 {
11897 dc_stuff_float(&Wing_scale);
11898 }
11899
11900 /**
11901 * Given an object to fly off of, a wing and a position in the wing formation, return the desired absolute location to fly to.
11902 * @return result in *result_pos.
11903 */
get_absolute_wing_pos(vec3d * result_pos,object * leader_objp,int wingnum,int wing_index,bool formation_object_flag,bool autopilot)11904 void get_absolute_wing_pos(vec3d *result_pos, object *leader_objp, int wingnum, int wing_index, bool formation_object_flag, bool autopilot)
11905 {
11906 vec3d wing_delta, rotated_wing_delta;
11907 float wing_spread_size;
11908 int formation_index = Wings[wingnum].formation;
11909
11910 if (wing_index == 0)
11911 wing_delta = vmd_zero_vector;
11912 else if (!Wing_formations.empty() && (formation_index >= 0) && (wing_index < MAX_SHIPS_PER_WING)) {
11913 Assertion((int)Wing_formations.size() > formation_index, "%s wing's formation_index %d is higher than the Wing_formations size. Something went very wrong, go tell a coder!", Wings[wingnum].name, formation_index);
11914 wing_delta = Wing_formations[formation_index].positions[wing_index - 1]; // custom desired location in leader's reference frame
11915 }
11916 else
11917 get_wing_delta(&wing_delta, wing_index); // default desired location in leader's reference frame
11918
11919 wing_spread_size = MAX(50.0f, 3.0f * get_wing_largest_radius(leader_objp, formation_object_flag) + 15.0f);
11920
11921 // if not autopilot apply debug modifications to spread size and also y scale (2x default) unless it's a custom position
11922 if (leader_objp->flags[Object::Object_Flags::Player_ship] && !autopilot) {
11923 if(formation_index == -1)
11924 wing_delta.xyz.y *= Wing_y_scale;
11925 wing_spread_size *= Wing_scale;
11926 }
11927
11928 float scale_factor = autopilot ? 1.5f : (1.0f + leader_objp->phys_info.speed / 70.0f);
11929 vm_vec_scale(&wing_delta, wing_spread_size * scale_factor);
11930
11931 vm_vec_unrotate(&rotated_wing_delta, &wing_delta, &leader_objp->orient); // Rotate into leader's reference.
11932 vm_vec_add(result_pos, &leader_objp->pos, &rotated_wing_delta); // goal_point is absolute 3-space point.
11933 }
11934
11935 #ifndef NDEBUG
11936 int Debug_render_wing_phantoms;
11937
render_wing_phantoms(object * objp)11938 void render_wing_phantoms(object *objp)
11939 {
11940 int i;
11941 ship *shipp;
11942 ai_info *aip;
11943 int wingnum;
11944 int wing_index; // Index in wing struct, defines 3-space location in wing.
11945 vec3d goal_point;
11946
11947 Assert(objp->type == OBJ_SHIP);
11948 Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
11949
11950 shipp = &Ships[objp->instance];
11951 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
11952
11953 aip = &Ai_info[shipp->ai_index];
11954
11955 wingnum = aip->wing;
11956
11957 if (wingnum == -1)
11958 return;
11959
11960 wing_index = get_wing_index(objp, wingnum);
11961
11962 // If this ship is NOT the leader, abort.
11963 if (wing_index != 0)
11964 return;
11965
11966 for (i=0; i<32; i++)
11967 if (Debug_render_wing_phantoms & (1 << i)) {
11968 get_absolute_wing_pos(&goal_point, objp, wingnum, i, false);
11969
11970 vertex vert;
11971 gr_set_color(255, 0, 128);
11972 g3_rotate_vertex(&vert, &goal_point);
11973 g3_draw_sphere(&vert, 2.0f);
11974 }
11975
11976 Debug_render_wing_phantoms = 0;
11977
11978 }
11979
render_wing_phantoms_all()11980 void render_wing_phantoms_all()
11981 {
11982 object *objp;
11983 ship_obj *so;
11984
11985 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
11986 ship *shipp;
11987 ai_info *aip;
11988 int wingnum;
11989 int wing_index; // Index in wing struct, defines 3-space location in wing.
11990
11991 objp = &Objects[so->objnum];
11992
11993 Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
11994 shipp = &Ships[objp->instance];
11995 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
11996
11997 aip = &Ai_info[shipp->ai_index];
11998
11999 wingnum = aip->wing;
12000
12001 if (wingnum == -1)
12002 continue;
12003
12004 wing_index = get_wing_index(objp, wingnum);
12005
12006 // If this ship is NOT the leader, abort.
12007 if (wing_index != 0)
12008 continue;
12009
12010 render_wing_phantoms(objp);
12011
12012 return;
12013 }
12014 }
12015
12016 #endif
12017
12018 float Leader_chaos = 0.0f;
12019 int Chaos_frame = -1;
12020
12021 // Return true if objp is flying in an erratic manner
12022 // Only true if objp is a player
formation_is_leader_chaotic(object * objp)12023 int formation_is_leader_chaotic(object *objp)
12024 {
12025 if (Game_mode & GM_MULTIPLAYER)
12026 return 0;
12027
12028 if (objp != Player_obj)
12029 return 0;
12030
12031 if (Framecount != Chaos_frame) {
12032 float speed_scale;
12033 float fdot, udot;
12034
12035 speed_scale = 3.0f + objp->phys_info.speed * 0.1f;
12036
12037 fdot = 5.0f * (1.0f - vm_vec_dot(&objp->orient.vec.fvec, &objp->last_orient.vec.fvec)) * flFrametime;
12038 udot = 8.0f * (1.0f - vm_vec_dot(&objp->orient.vec.uvec, &objp->last_orient.vec.uvec)) * flFrametime;
12039
12040 Leader_chaos += fdot * speed_scale + udot * speed_scale;
12041
12042 Leader_chaos *= (1.0f - flFrametime*0.2f);
12043
12044 CLAMP(Leader_chaos, 0.0f, 1.7f);
12045
12046 Chaos_frame = Framecount;
12047 }
12048
12049 return (Leader_chaos > 1.0f);
12050 }
12051
ai_most_massive_object_of_its_wing_of_all_docked_objects_helper(object * objp,dock_function_info * infop)12052 void ai_most_massive_object_of_its_wing_of_all_docked_objects_helper(object *objp, dock_function_info *infop)
12053 {
12054 // check that I am a ship
12055 if (objp->type == OBJ_SHIP)
12056 {
12057 // check that wings match
12058 if (Ai_info[Ships[objp->instance].ai_index].wing == infop->parameter_variables.int_value)
12059 {
12060 // if this guy has a higher mass, he is now the most massive object
12061 if (objp->phys_info.mass > infop->maintained_variables.objp_value->phys_info.mass)
12062 {
12063 infop->maintained_variables.objp_value = objp;
12064 }
12065 // if masses are equal, then check if this guy has a higher signature - if so, he is now the most massive object
12066 else if (objp->phys_info.mass == infop->maintained_variables.objp_value->phys_info.mass)
12067 {
12068 if (objp->signature > infop->maintained_variables.objp_value->signature)
12069 {
12070 infop->maintained_variables.objp_value = objp;
12071 }
12072 }
12073 }
12074 }
12075 }
12076
12077 // Fly in formation.
12078 // Make Pl_objp assume its proper place in formation.
12079 // If the leader of the wing is doing something stupid, like fighting a battle,
12080 // then the poor sap wingmates will be in for a "world of hurt"
12081 // Return TRUE if we need to process this object's normal mode
ai_formation()12082 int ai_formation()
12083 {
12084 object *leader_objp;
12085 ship *shipp;
12086 ai_info *aip, *laip;
12087 int wingnum;
12088 int wing_index; // Index in wing struct, defines 3-space location in wing.
12089 vec3d goal_point, future_goal_point_5, future_goal_point_2, future_goal_point_x, future_goal_point_1000x, vec_to_goal, dir_to_goal;
12090 float dot_to_goal, dist_to_goal, leader_speed;
12091
12092 Assert(Pl_objp->type == OBJ_SHIP);
12093 Assert((Pl_objp->instance >= 0) && (Pl_objp->instance < MAX_SHIPS));
12094
12095 shipp = &Ships[Pl_objp->instance];
12096
12097 Assert((shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO));
12098
12099 aip = &Ai_info[shipp->ai_index];
12100
12101 Assert(!(aip->ai_flags[AI::AI_Flags::Formation_wing] && aip->ai_flags[AI::AI_Flags::Formation_object])); // Make sure not both types of formation flying in effect.
12102
12103 // Determine which kind of formation flying.
12104 // If tracking an object, not in waypoint mode:
12105 if (aip->ai_flags[AI::AI_Flags::Formation_object]) {
12106 if ((aip->goal_objnum < 0) || (aip->goal_objnum >= MAX_OBJECTS) || (aip->mode == AIM_BAY_DEPART)) {
12107 aip->ai_flags.remove(AI::AI_Flags::Formation_object);
12108 return 1;
12109 }
12110
12111 wing_index = aip->form_obj_slotnum;
12112 leader_objp = &Objects[aip->goal_objnum];
12113 } else { // Formation flying in waypoint mode.
12114 Assert(aip->ai_flags[AI::AI_Flags::Formation_wing]);
12115
12116 if ( (aip->mode != AIM_WAYPOINTS) && (aip->mode != AIM_FLY_TO_SHIP) ) {
12117 aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
12118 return 1;
12119 }
12120
12121 wingnum = aip->wing;
12122
12123 if (wingnum == -1) {
12124 return 1;
12125 }
12126
12127 // disable formation flying for any ship in the players wing
12128 // ... except when using auto-pilot
12129 if ( !AutoPilotEngaged ) {
12130 if (wingnum == Ships[Player_obj->instance].wingnum) {
12131 return 1;
12132 }
12133 }
12134
12135 wing_index = get_wing_index(Pl_objp, wingnum);
12136
12137 leader_objp = get_wing_leader(wingnum);
12138 }
12139
12140 // if Pl_objp is docked with a ship in his own wing, only the most massive one
12141 // in the whole assembly actually flies in formation
12142 // Goober5000 - this is really stupid code
12143 if ( object_is_docked(Pl_objp) ) {
12144 // assume I am the most massive
12145 dock_function_info dfi;
12146 dfi.parameter_variables.int_value = aip->wing;
12147 dfi.maintained_variables.objp_value = Pl_objp;
12148
12149 // check docked objects
12150 dock_evaluate_all_docked_objects(Pl_objp, &dfi, ai_most_massive_object_of_its_wing_of_all_docked_objects_helper);
12151
12152 // if I am not the most massive, return
12153 if (dfi.maintained_variables.objp_value != Pl_objp)
12154 {
12155 return 0;
12156 }
12157 }
12158
12159 Assert(leader_objp != NULL);
12160 laip = &Ai_info[Ships[leader_objp->instance].ai_index];
12161
12162 // Make sure we're really in this wing.
12163 if (wing_index == -1) {
12164 return 1;
12165 }
12166
12167 // skip ourselves, if we happen to be the leader
12168 if (leader_objp == Pl_objp) {
12169 return 1;
12170 }
12171
12172 if (aip->mode == AIM_WAYPOINTS) {
12173
12174 if (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_ai_path_order_bug]){
12175 // skip if wing leader has no waypoint order or a different waypoint list
12176 if ((laip->mode != AIM_WAYPOINTS) || !(aip->wp_list == laip->wp_list)){
12177 return 1;
12178 }
12179 }
12180
12181 aip->wp_list = laip->wp_list;
12182 aip->wp_index = laip->wp_index;
12183 aip->wp_flags = laip->wp_flags;
12184
12185 if ((aip->wp_list != NULL) && (aip->wp_index == aip->wp_list->get_waypoints().size()))
12186 --aip->wp_index;
12187 }
12188
12189 #ifndef NDEBUG
12190 Debug_render_wing_phantoms |= (1 << wing_index);
12191 #endif
12192
12193 leader_speed = leader_objp->phys_info.speed;
12194 vec3d leader_vec = leader_objp->phys_info.vel;
12195
12196 get_absolute_wing_pos(&goal_point, leader_objp, aip->wing, wing_index, aip->ai_flags[AI::AI_Flags::Formation_object]);
12197 vm_vec_scale_add(&future_goal_point_5, &goal_point, &leader_vec, 10.0f);
12198 vm_vec_scale_add(&future_goal_point_2, &goal_point, &leader_vec, 5.0f);
12199 vm_vec_scale_add(&future_goal_point_x, &goal_point, &leader_objp->orient.vec.fvec, 10.0f); // used when very close to destination
12200 vm_vec_scale_add(&future_goal_point_1000x, &goal_point, &leader_objp->orient.vec.fvec, 1000.0f); // used when very close to destination
12201
12202 // Now, get information telling this object how to turn and accelerate to get to its
12203 // desired location.
12204 vm_vec_sub(&vec_to_goal, &goal_point, &Pl_objp->pos);
12205 if ( vm_vec_mag_quick(&vec_to_goal) < AICODE_SMALL_MAGNITUDE )
12206 vec_to_goal.xyz.x += 0.1f;
12207
12208 vm_vec_copy_normalize(&dir_to_goal, &vec_to_goal);
12209 dot_to_goal = vm_vec_dot(&dir_to_goal, &Pl_objp->orient.vec.fvec);
12210 dist_to_goal = vm_vec_dist_quick(&Pl_objp->pos, &goal_point);
12211 float dist_to_goal_2 = vm_vec_dist_quick(&Pl_objp->pos, &future_goal_point_2);
12212
12213 // See how different this ship's bank is relative to wing leader
12214 // If it's too different, set leader_orient in subsequent calls to turn_towards_point
12215 // (nullptr will be ignored otherwise)
12216 float leader_up_dot = vm_vec_dot(&leader_objp->orient.vec.uvec, &Pl_objp->orient.vec.uvec);
12217 matrix* leader_orient = nullptr;
12218 if (leader_up_dot < 0.996f) {
12219 leader_orient = &leader_objp->orient;
12220 }
12221
12222 int chaotic_leader = 0;
12223
12224 chaotic_leader = formation_is_leader_chaotic(leader_objp); // Set to 1 if leader is player and flying erratically. Causes ships to not aggressively pursue formation location.
12225
12226 ship_info *sip = &Ship_info[shipp->ship_info_index];
12227 bool ab_allowed = false;
12228 if ((sip->flags[Ship::Info_Flags::Afterburner]) && !(shipp->flags[Ship::Ship_Flags::Afterburner_locked]) && (aip->ai_flags[AI::AI_Flags::Free_afterburner_use] || aip->ai_profile_flags[AI::Profile_Flags::Free_afterburner_use])) {
12229 ab_allowed = true;
12230 } else {
12231 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12232 afterburners_stop(Pl_objp);
12233 }
12234
12235 float switch_value = 0.0f;
12236 if (ab_allowed) {
12237 if ((Pl_objp->phys_info.afterburner_max_vel.xyz.z - leader_speed) > (0.2f * Pl_objp->phys_info.afterburner_max_vel.xyz.z)) {
12238 switch_value = 0.2f * Pl_objp->phys_info.afterburner_max_vel.xyz.z;
12239 } else {
12240 switch_value = Pl_objp->phys_info.afterburner_max_vel.xyz.z - leader_speed;
12241 }
12242 }
12243
12244 if (dist_to_goal > 500.0f) {
12245 turn_towards_point(Pl_objp, &goal_point, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12246 if (ab_allowed && !(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && (dot_to_goal > 0.2f)) {
12247 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
12248 if (percent_left > 30.0f) {
12249 afterburners_start(Pl_objp);
12250 aip->afterburner_stop_time = Missiontime + 5 * F1_0;
12251 }
12252 }
12253 accelerate_ship(aip, 1.0f);
12254 } else if (dist_to_goal > 200.0f) {
12255 if (dot_to_goal > -0.5f) {
12256 turn_towards_point(Pl_objp, &goal_point, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12257 float range_speed = shipp->current_max_speed - leader_speed;
12258 if (range_speed > 0.0f) {
12259 set_accel_for_target_speed(Pl_objp, leader_speed + range_speed * (dist_to_goal+100.0f)/500.0f);
12260 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12261 afterburners_stop(Pl_objp);
12262 } else {
12263 set_accel_for_target_speed(Pl_objp, shipp->current_max_speed);
12264 if (ab_allowed) {
12265 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && (dot_to_goal > 0.4f)) {
12266 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
12267 if ((percent_left > 30.0f) && (Pl_objp->phys_info.speed < leader_speed)) {
12268 afterburners_start(Pl_objp);
12269 aip->afterburner_stop_time = Missiontime + 5 * F1_0;
12270 }
12271 } else {
12272 if ((Pl_objp->phys_info.speed > leader_speed + 1.5 * switch_value) || (dot_to_goal < 0.2f))
12273 afterburners_stop(Pl_objp);
12274 }
12275 }
12276 }
12277 } else {
12278 turn_towards_point(Pl_objp, &future_goal_point_5, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12279 if (leader_speed > 10.0f)
12280 set_accel_for_target_speed(Pl_objp, leader_speed *(1.0f + dot_to_goal));
12281 else
12282 set_accel_for_target_speed(Pl_objp, 10.0f);
12283 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12284 afterburners_stop(Pl_objp);
12285
12286 }
12287 } else {
12288 vec3d v2f2;
12289 float dot_to_f2;
12290
12291 vm_vec_normalized_dir(&v2f2, &future_goal_point_2, &Pl_objp->pos);
12292 dot_to_f2 = vm_vec_dot(&v2f2, &Pl_objp->orient.vec.fvec);
12293
12294 // Leader flying like a maniac. Don't try hard to form on wing.
12295 if (chaotic_leader) {
12296 turn_towards_point(Pl_objp, &future_goal_point_2, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12297 set_accel_for_target_speed(Pl_objp, MIN(leader_speed*0.8f, 20.0f));
12298 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12299 afterburners_stop(Pl_objp);
12300 } else if (dist_to_goal > 75.0f) {
12301 turn_towards_point(Pl_objp, &future_goal_point_2, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12302 float delta_speed;
12303 float range_speed = shipp->current_max_speed - leader_speed;
12304 if (range_speed > 0.0f) {
12305 delta_speed = dist_to_goal_2/500.0f * range_speed;
12306 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12307 afterburners_stop(Pl_objp);
12308 } else {
12309 delta_speed = shipp->current_max_speed - leader_speed;
12310 if (ab_allowed) {
12311 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && (dot_to_goal > 0.6f)) {
12312 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
12313 if ((percent_left > 30.0f) && (Pl_objp->phys_info.speed < leader_speed - 0.5 * switch_value)) {
12314 afterburners_start(Pl_objp);
12315 aip->afterburner_stop_time = Missiontime + 5 * F1_0;
12316 }
12317 } else {
12318 if ((Pl_objp->phys_info.speed > leader_speed + 0.95 * switch_value) || (dot_to_goal < 0.4f))
12319 afterburners_stop(Pl_objp);
12320 }
12321 }
12322 }
12323 if (dot_to_goal < 0.0f) {
12324 delta_speed = -delta_speed;
12325 if (-delta_speed > leader_speed/2)
12326 delta_speed = -leader_speed/2;
12327 }
12328
12329 if (leader_speed < 5.0f)
12330 if (delta_speed < 5.0f)
12331 delta_speed = 5.0f;
12332
12333 float scale = dot_to_f2;
12334 if (scale < 0.1f)
12335 scale = 0.0f;
12336 else
12337 scale *= scale;
12338
12339 set_accel_for_target_speed(Pl_objp, scale * (leader_speed + delta_speed));
12340 } else {
12341
12342 if (leader_speed < 5.0f) {
12343 // Leader very slow. If not close to goal point, get very close. Note, keep trying to get close unless
12344 // moving very slowly, else momentum can carry far away from goal.
12345
12346 if ((dist_to_goal > 10.0f) || ((Pl_objp->phys_info.speed > leader_speed + 2.5f) && (dot_to_goal > 0.5f))) {
12347 turn_towards_point(Pl_objp, &goal_point, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12348 set_accel_for_target_speed(Pl_objp, leader_speed + dist_to_goal/10.0f);
12349 } else {
12350 if (Pl_objp->phys_info.speed < 0.5f) {
12351 turn_towards_point(Pl_objp, &future_goal_point_1000x, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12352 }
12353 set_accel_for_target_speed(Pl_objp, leader_speed);
12354 }
12355 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12356 afterburners_stop(Pl_objp);
12357
12358 } else if (dist_to_goal > 10.0f) {
12359 float dv;
12360
12361 turn_towards_point(Pl_objp, &future_goal_point_2, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12362
12363 if (dist_to_goal > 25.0f) {
12364 if (dot_to_goal < 0.3f)
12365 dv = -0.1f;
12366 else
12367 dv = dot_to_goal - 0.2f;
12368
12369 set_accel_for_target_speed(Pl_objp, leader_speed + dist_to_goal/5.0f * dv);
12370 } else {
12371 set_accel_for_target_speed(Pl_objp, leader_speed + 1.5f * dot_to_goal - 1.0f);
12372 }
12373
12374 float range_speed = shipp->current_max_speed - leader_speed;
12375 if (range_speed > 0.0f) {
12376 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12377 afterburners_stop(Pl_objp);
12378 } else {
12379 if (ab_allowed) {
12380 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && (dot_to_goal > 0.9f)) {
12381 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
12382 if ((percent_left > 30.0f) && (Pl_objp->phys_info.speed < leader_speed - 0.75 * switch_value)) {
12383 afterburners_start(Pl_objp);
12384 aip->afterburner_stop_time = Missiontime + 3 * F1_0;
12385 }
12386 } else {
12387 if ((Pl_objp->phys_info.speed > leader_speed + 0.95 * switch_value) || (dot_to_goal < 0.7f))
12388 afterburners_stop(Pl_objp);
12389 }
12390 }
12391 }
12392
12393 } else {
12394 if (Pl_objp->phys_info.speed < 0.1f)
12395 turn_towards_point(Pl_objp, &future_goal_point_1000x, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12396 else
12397 turn_towards_point(Pl_objp, &future_goal_point_x, nullptr, 0.0f, leader_orient, AITTV_SLOW_BANK_ACCEL);
12398
12399 // Goober5000 7/5/2006 changed to leader_speed from 0.0f
12400 set_accel_for_target_speed(Pl_objp, leader_speed);
12401
12402 float range_speed = shipp->current_max_speed - leader_speed;
12403 if (range_speed > 0.0f) {
12404 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON)
12405 afterburners_stop(Pl_objp);
12406 } else {
12407 if (ab_allowed) {
12408 if (!(Pl_objp->phys_info.flags & PF_AFTERBURNER_ON) && (dot_to_goal > 0.9f)) {
12409 float percent_left = 100.0f * shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
12410 if ((percent_left > 30.0f) && (Pl_objp->phys_info.speed < leader_speed - 0.75 * switch_value)) {
12411 afterburners_start(Pl_objp);
12412 aip->afterburner_stop_time = Missiontime + 3 * F1_0;
12413 }
12414 } else {
12415 if ((Pl_objp->phys_info.speed > leader_speed + 0.75 * switch_value) || (dot_to_goal < 0.8f))
12416 afterburners_stop(Pl_objp);
12417 }
12418 }
12419 }
12420
12421 }
12422 }
12423
12424 }
12425
12426 return 0;
12427 }
12428
12429 /**
12430 * If object *objp is being repaired, deal with it!
12431 */
ai_do_repair_frame(object * objp,ai_info * aip,float frametime)12432 void ai_do_repair_frame(object *objp, ai_info *aip, float frametime)
12433 {
12434 static bool rearm_eta_found=false;
12435
12436
12437 if (aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair]) {
12438 if (Ships[objp->instance].team == Iff_traitor) {
12439 ai_abort_rearm_request(objp);
12440 return;
12441 }
12442
12443 int support_objnum;
12444
12445 ai_info *repair_aip;
12446
12447 support_objnum = aip->support_ship_objnum;
12448
12449 if (support_objnum == -1)
12450 return;
12451
12452 Assertion(support_objnum >= 0, "The support ship objnum is nonsense. It is %d instead of a positive number or -1.", support_objnum);
12453
12454 // Curious -- object numbers match, but signatures do not.
12455 // Must mean original repair ship died and was replaced by current ship.
12456 Assert(Objects[support_objnum].signature == aip->support_ship_signature);
12457
12458 repair_aip = &Ai_info[Ships[Objects[support_objnum].instance].ai_index];
12459
12460 if (aip->ai_flags[AI::AI_Flags::Being_repaired]) {
12461
12462 // Wait awhile into the mode to synchronize with sound effect.
12463 if (Missiontime - repair_aip->submode_start_time > REARM_SOUND_DELAY) {
12464 int repaired;
12465
12466 if (!rearm_eta_found && objp==Player_obj && Player_rearm_eta <= 0)
12467 {
12468 rearm_eta_found = true;
12469 Player_rearm_eta = ship_calculate_rearm_duration(objp);
12470 }
12471
12472 repaired = ship_do_rearm_frame( objp, frametime ); // hook to do missile rearming
12473
12474 // See if fully repaired. If so, cause process to stop.
12475 if ( repaired && (repair_aip->submode == AIS_DOCK_4)) {
12476
12477 repair_aip->submode = AIS_UNDOCK_0;
12478 repair_aip->submode_start_time = Missiontime;
12479
12480 // if repairing player object -- tell him done with repair
12481 if ( !MULTIPLAYER_CLIENT ) {
12482 ai_do_objects_repairing_stuff( objp, &Objects[support_objnum], REPAIR_INFO_COMPLETE );
12483 }
12484 }
12485 }
12486 } else if (aip->ai_flags[AI::AI_Flags::Awaiting_repair]) {
12487 // If this ship has been awaiting repair for 90+ seconds, abort.
12488 if ( !MULTIPLAYER_CLIENT ) {
12489 if ((Game_mode & GM_MULTIPLAYER) || (objp != Player_obj)) {
12490 if ((repair_aip->goal_objnum == OBJ_INDEX(objp)) && (timestamp_elapsed(aip->abort_rearm_timestamp))) {
12491 ai_abort_rearm_request(objp);
12492 aip->next_rearm_request_timestamp = timestamp(NEXT_REARM_TIMESTAMP);
12493 }
12494 }
12495 }
12496 }
12497 } else {
12498 // AL 11-24-97: If this is the player ship, ensure the repair sound isn't playing. We need to
12499 // do this check, since this is a looping sound, and may continue on if rearm/repair
12500 // finishes abnormally once sound begins looping.
12501 if ( objp == Player_obj ) {
12502 Player_rearm_eta = 0;
12503 rearm_eta_found = false;
12504 player_stop_repair_sound();
12505 }
12506 }
12507 }
12508
12509 // Shell around dock_orient_and_approach to detect whether dock process should be aborted.
12510 // Goober5000 - The child should always keep up with the parent.
call_doa(object * child,object * parent)12511 void call_doa(object *child, object *parent)
12512 {
12513 Assert(dock_check_find_direct_docked_object(parent, child));
12514
12515 // get indexes
12516 int parent_index = dock_find_dockpoint_used_by_object(parent, child);
12517 int child_index = dock_find_dockpoint_used_by_object(child, parent);
12518
12519 // child keeps up with parent
12520 dock_orient_and_approach(child, child_index, parent, parent_index, DOA_DOCK_STAY);
12521 }
12522
12523 // Maybe launch a countermeasure.
12524 // Also, detect a supposed homing missile that no longer exists.
ai_maybe_launch_cmeasure(object * objp,ai_info * aip)12525 void ai_maybe_launch_cmeasure(object *objp, ai_info *aip)
12526 {
12527 float dist;
12528 ship_info *sip;
12529 ship *shipp;
12530
12531 shipp = &Ships[objp->instance];
12532 sip = &Ship_info[shipp->ship_info_index];
12533
12534 if (!(sip->is_small_ship() || sip->flags[Ship::Info_Flags::Transport]))
12535 return;
12536
12537 if (object_is_docked(objp))
12538 return;
12539
12540 if (!shipp->cmeasure_count)
12541 return;
12542
12543 if ( !timestamp_elapsed(shipp->cmeasure_fire_stamp) )
12544 return;
12545
12546 // If not on player's team and Skill_level + ai_class is low, never fire a countermeasure. The ship is too dumb.
12547 if (iff_x_attacks_y(Player_ship->team, shipp->team)) {
12548 //SUSHI: Only bail if autoscale is on...
12549 if (aip->ai_class_autoscale && Game_skill_level + aip->ai_class < 4){
12550 return;
12551 }
12552 }
12553
12554 if ((aip->nearest_locked_object != -1) && (Objects[aip->nearest_locked_object].type == OBJ_WEAPON)) {
12555 object *weapon_objp;
12556
12557 weapon_objp = &Objects[aip->nearest_locked_object];
12558
12559 dist = vm_vec_dist(&objp->pos, &weapon_objp->pos);
12560
12561 bool in_countermeasure_range = dist < weapon_objp->phys_info.speed * 2.0f;
12562 weapon_info *cmeasure = &Weapon_info[shipp->current_cmeasure];
12563
12564 if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance])
12565 in_countermeasure_range = dist < cmeasure->cm_effective_rad;
12566
12567 if ( in_countermeasure_range ) {
12568
12569 aip->nearest_locked_distance = dist;
12570 // Verify that this object is really homing on us.
12571
12572 float fire_chance;
12573
12574 // For ships on player's team, check if modder wants default constant chance to fire or value from specific AI class profile.
12575 // For enemies, use value from specific AI class profile (usually increasing chance with higher skill level).
12576 if ( (shipp->team == Player_ship->team) && !(aip->ai_profile_flags[AI::Profile_Flags::Friendlies_use_countermeasure_firechance]) ) {
12577 fire_chance = The_mission.ai_profile->cmeasure_fire_chance[NUM_SKILL_LEVELS/2];
12578 } else {
12579 fire_chance = aip->ai_cmeasure_fire_chance;
12580 }
12581
12582 // Decrease chance to fire at lower ai class (SUSHI: Only if autoscale is on)
12583 if (aip->ai_class_autoscale)
12584 fire_chance *= (float) aip->ai_class/Num_ai_classes;
12585
12586 float r = frand();
12587 if (fire_chance < r) {
12588 int fail_delay;
12589 // check if modder wants to use custom fail delay, or default
12590 if (cmeasure->cmeasure_failure_delay_multiplier_ai >= 0) {
12591 // use firewait as delay
12592 fail_delay = cmeasure->cmeasure_firewait * cmeasure->cmeasure_failure_delay_multiplier_ai;
12593 } else {
12594 // use default to wait firwait + additional delay to decrease chance of firing very soon.
12595 fail_delay = cmeasure->cmeasure_firewait + (int) (fire_chance*2000);
12596 }
12597 shipp->cmeasure_fire_stamp = timestamp(fail_delay);
12598 return;
12599 }
12600
12601 if (weapon_objp->type == OBJ_WEAPON) {
12602 if (weapon_objp->instance >= 0) {
12603 ship_launch_countermeasure(objp);
12604 shipp->cmeasure_fire_stamp = timestamp(cmeasure->cmeasure_firewait * cmeasure->cmeasure_sucess_delay_multiplier_ai);
12605 return;
12606 }
12607 }
12608
12609 }
12610 }
12611
12612 return;
12613 }
12614
12615 // --------------------------------------------------------------------------
ai_preprocess_ignore_objnum(ai_info * aip)12616 static void ai_preprocess_ignore_objnum(ai_info *aip)
12617 {
12618 if (aip->ai_flags[AI::AI_Flags::Temporary_ignore])
12619 {
12620 if (timestamp_elapsed(aip->ignore_expire_timestamp))
12621 aip->ignore_objnum = UNUSED_OBJNUM;
12622 }
12623
12624 if (is_ignore_object(aip, aip->goal_objnum))
12625 {
12626 // you can land and launch on a ship you're ignoring!
12627 if (aip->mode != AIM_BAY_EMERGE && aip->mode != AIM_BAY_DEPART) {
12628 aip->goal_objnum = -1;
12629 }
12630
12631 // AL 12-11-97: If in STRAFE mode, we need to ensure that target_objnum is also
12632 // set to -1
12633 if (aip->mode == AIM_STRAFE)
12634 aip->target_objnum = -1;
12635 }
12636
12637 if (is_ignore_object(aip, aip->target_objnum))
12638 aip->target_objnum = -1;
12639 }
12640
12641 #define CHASE_CIRCLE_DIST 100.0f
12642
ai_chase_circle(object * objp)12643 void ai_chase_circle(object *objp)
12644 {
12645 float dist_to_goal;
12646 float target_speed;
12647 vec3d goal_point;
12648 ship_info *sip;
12649 ai_info *aip;
12650
12651 sip = &Ship_info[Ships[Pl_objp->instance].ship_info_index];
12652
12653 target_speed = sip->max_speed/4.0f;
12654 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
12655
12656 Assert(vm_vec_mag(&aip->goal_point) >= 0.0f); // Supposedly detects bogus vector
12657
12658 goal_point = aip->goal_point;
12659
12660 if (aip->ignore_objnum == UNUSED_OBJNUM) {
12661 dist_to_goal = vm_vec_dist_quick(&aip->goal_point, &objp->pos);
12662
12663 if (dist_to_goal > 2*CHASE_CIRCLE_DIST) {
12664 vec3d vec_to_goal;
12665 // Too far from circle goal, create a new goal point.
12666 vm_vec_normalized_dir(&vec_to_goal, &aip->goal_point, &objp->pos);
12667 vm_vec_scale_add(&aip->goal_point, &objp->pos, &vec_to_goal, CHASE_CIRCLE_DIST);
12668 }
12669
12670 goal_point = aip->goal_point;
12671 } else if (is_ignore_object(aip, aip->ignore_objnum)) {
12672 object *ignore_objp = &Objects[aip->ignore_objnum];
12673
12674 vec3d tvec1;
12675 float dist;
12676
12677 dist = vm_vec_normalized_dir(&tvec1, &Pl_objp->pos, &ignore_objp->pos);
12678
12679 if (dist < ignore_objp->radius*2 + 1500.0f) {
12680 vm_vec_scale_add(&goal_point, &Pl_objp->pos, &tvec1, ignore_objp->radius*2 + 1400.0f);
12681 if (dist < ignore_objp->radius*2 + 1300.0f)
12682 target_speed = sip->max_speed * (1.25f - dist/(ignore_objp->radius*2 + 1500.0f));
12683 }
12684 }
12685
12686 Assert(vm_vec_mag(&aip->goal_point) >= 0.0f); // Supposedly detects bogus vector
12687
12688 turn_towards_tangent(Pl_objp, &goal_point, 10*objp->radius + 200.0f);
12689
12690 set_accel_for_target_speed(Pl_objp, target_speed);
12691
12692 }
12693
12694 #define SHIELD_BALANCE_RATE 0.2f // 0.1f -> takes 10 seconds to equalize shield.
12695
12696 /**
12697 * Transfer shield energy to most recently hit section from others.
12698 */
ai_transfer_shield(object * objp,int quadrant_num)12699 void ai_transfer_shield(object *objp, int quadrant_num)
12700 {
12701 shield_transfer(objp, quadrant_num, (SHIELD_BALANCE_RATE / 2));
12702 }
12703
ai_balance_shield(object * objp)12704 void ai_balance_shield(object *objp)
12705 {
12706 shield_balance(objp, SHIELD_BALANCE_RATE, 0.0f);
12707 }
12708
12709 // Manage the shield for this ship.
12710 // Try to max out the side that was most recently hit.
ai_manage_shield(object * objp,ai_info * aip)12711 void ai_manage_shield(object *objp, ai_info *aip)
12712 {
12713 ship_info *sip;
12714
12715 sip = &Ship_info[Ships[objp->instance].ship_info_index];
12716
12717 if (timestamp_elapsed(aip->shield_manage_timestamp)) {
12718 float delay;
12719
12720 // Scale time until next manage shield based on Skill_level.
12721 // Ships on player's team are treated as if Skill_level is average.
12722 if (iff_x_attacks_y(Player_ship->team, Ships[objp->instance].team))
12723 {
12724 delay = aip->ai_shield_manage_delay;
12725 }
12726 else
12727 {
12728 delay = The_mission.ai_profile->shield_manage_delay[NUM_SKILL_LEVELS/2];
12729 }
12730
12731 // Scale between 1x and 3x based on ai_class (SUSHI: only if autoscale is on)
12732 if (aip->ai_class_autoscale)
12733 delay = delay + delay * (float) (3*(Num_ai_classes - aip->ai_class - 1) / (Num_ai_classes - 1));
12734
12735 // set timestamp
12736 aip->shield_manage_timestamp = timestamp((int) (delay * 1000.0f));
12737
12738 if (sip->is_small_ship() || (aip->ai_profile_flags[AI::Profile_Flags::All_ships_manage_shields])) {
12739 if (Missiontime - aip->last_hit_time < F1_0*10)
12740 ai_transfer_shield(objp, aip->last_hit_quadrant);
12741 else
12742 ai_balance_shield(objp);
12743 }
12744 }
12745 }
12746
12747 // See if object *objp should evade an incoming missile.
12748 // *aip is the ai_info pointer within *objp.
ai_maybe_evade_locked_missile(object * objp,ai_info * aip)12749 void ai_maybe_evade_locked_missile(object *objp, ai_info *aip)
12750 {
12751 ship *shipp;
12752 shipp = &Ships[objp->instance];
12753
12754 // Only small ships evade an incoming missile. Why would a capital ship try to swerve?
12755 if (!Ship_info[Ships[objp->instance].ship_info_index].is_small_ship()) {
12756 return;
12757 }
12758
12759 // don't evade missiles if you're docked
12760 if (object_is_docked(objp)) {
12761 return;
12762 }
12763
12764 if (aip->ai_flags[AI::AI_Flags::No_dynamic, AI::AI_Flags::Kamikaze]) { // If not allowed to pursue dynamic objectives, don't evade. Dumb? Maybe change. -- MK, 3/15/98
12765 return;
12766 }
12767
12768 if (aip->nearest_locked_object != -1) {
12769 object *missile_objp;
12770
12771 missile_objp = &Objects[aip->nearest_locked_object];
12772
12773 if (Weapons[missile_objp->instance].homing_object != objp) {
12774 aip->nearest_locked_object = -1;
12775 return;
12776 }
12777
12778 if ((missile_objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[missile_objp->instance].weapon_info_index].is_homing())) {
12779
12780 vec3d v2m;
12781 float dist = vm_vec_normalized_dir(&v2m, &objp->pos, &missile_objp->pos);
12782
12783 if (The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]
12784 && vm_vec_dot(&v2m, &missile_objp->orient.vec.fvec) < 0.5f ) {
12785 // don't bother if the missile isn't actually coming towards us
12786 aip->nearest_locked_object = -1;
12787 return;
12788 }
12789
12790 // evade missiles 4 seconds away normally, 2 seconds away with the flag (evading too early is a thing!)
12791 float evade_dist_scalar = The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance] ? 2.0f : 4.0f;
12792 float evade_distance = evade_dist_scalar * vm_vec_mag_quick(&missile_objp->phys_info.vel);
12793 if (dist < evade_distance) {
12794 switch (aip->mode) {
12795 // If in AIM_STRAFE mode, don't evade if parent of weapon is targeted ship.
12796 case AIM_STRAFE:
12797 if ((missile_objp->parent != -1) && (missile_objp->parent == aip->target_objnum)) {
12798 ;
12799 } else {
12800 ; // Alan -- If you want to handle incoming weapons from someone other than the ship
12801 // the strafing ship is attacking, do it here.
12802 }
12803 break;
12804 case AIM_CHASE:
12805 // Don't always go into evade weapon mode. Usually, a countermeasure gets launched.
12806 // If low on countermeasures, more likely to try to evade. If 8+, never evade due to low cmeasures.
12807 // Asteroth - If Improved_missile_avoidance, always evade!! Don't assume anything else will save you
12808 if (((((Missiontime >> 18) ^ OBJ_INDEX(objp)) & 3) == 0) ||
12809 (objp->phys_info.speed < 40.0f) ||
12810 (frand() < 1.0f - (float) shipp->cmeasure_count/8.0f ||
12811 The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance])) {
12812 if (aip->submode != SM_ATTACK_FOREVER) { // SM_ATTACK_FOREVER means engines blown.
12813 aip->submode = SM_EVADE_WEAPON;
12814 aip->submode_start_time = Missiontime;
12815 }
12816 }
12817 break;
12818 case AIM_DOCK: // Ships in dock mode can evade iif they are not currently repairing or docked.
12819 if (object_is_docked(objp) || (aip->ai_flags[AI::AI_Flags::Repairing, AI::AI_Flags::Being_repaired]))
12820 break;
12821 FALLTHROUGH;
12822 case AIM_GUARD:
12823 // If in guard mode and far away from guard object, don't pursue guy that hit me.
12824 if ((aip->guard_objnum != -1) && (aip->guard_signature == Objects[aip->guard_objnum].signature)) {
12825 if (vm_vec_dist_quick(&objp->pos, &Objects[aip->guard_objnum].pos) > 500.0f) {
12826 return;
12827 }
12828 }
12829 FALLTHROUGH;
12830 case AIM_EVADE:
12831 case AIM_GET_BEHIND:
12832 case AIM_STAY_NEAR:
12833 case AIM_STILL:
12834 case AIM_AVOID:
12835 case AIM_WAYPOINTS:
12836 case AIM_NONE:
12837 case AIM_BIGSHIP:
12838 case AIM_PATH:
12839 case AIM_BE_REARMED:
12840 case AIM_SAFETY:
12841 case AIM_BAY_EMERGE:
12842 case AIM_FLY_TO_SHIP:
12843 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
12844 aip->previous_mode = aip->mode;
12845 aip->previous_submode = aip->submode;
12846 aip->mode = AIM_EVADE_WEAPON;
12847 aip->submode = -1;
12848 aip->submode_start_time = Missiontime;
12849 aip->mode_time = timestamp(MAX_EVADE_TIME); // Max time to evade.
12850 break;
12851 case AIM_EVADE_WEAPON: // Note: We don't want to change mode on another evasion, or previous_mode will get bashed.
12852 case AIM_PLAY_DEAD:
12853 case AIM_BAY_DEPART:
12854 case AIM_SENTRYGUN:
12855 break;
12856 case AIM_WARP_OUT:
12857 break;
12858 default:
12859 Int3(); // Hey, what mode is it?
12860 break;
12861 }
12862 }
12863 } else {
12864 aip->nearest_locked_object = -1;
12865 }
12866 }
12867 }
12868
12869 // Maybe evade a dumbfire weapon that was fired when Pl_objp was targeted.
12870 // Have an 80% chance of evading in a second
maybe_evade_dumbfire_weapon(ai_info * aip)12871 void maybe_evade_dumbfire_weapon(ai_info *aip)
12872 {
12873 // Only small ships evade an incoming missile. Why would a capital ship try to swerve?
12874 if (!Ship_info[Ships[Pl_objp->instance].ship_info_index].is_small_ship()) {
12875 return;
12876 }
12877
12878 // don't evade missiles if you're docked
12879 if (object_is_docked(Pl_objp)) {
12880 return;
12881 }
12882
12883 // Make sure in a mode in which we evade dumbfire weapons.
12884 switch (aip->mode) {
12885 case AIM_CHASE:
12886 if (aip->submode == SM_ATTACK_FOREVER) {
12887 return;
12888 }
12889 break;
12890 case AIM_GUARD:
12891 // If in guard mode and far away from guard object, don't pursue guy that hit me.
12892 if ((aip->guard_objnum != -1) && (aip->guard_signature == Objects[aip->guard_objnum].signature)) {
12893 if (vm_vec_dist_quick(&Objects[Ships[aip->shipnum].objnum].pos, &Objects[aip->guard_objnum].pos) > 500.0f) {
12894 return;
12895 }
12896 }
12897 case AIM_STILL:
12898 case AIM_STAY_NEAR:
12899 case AIM_EVADE:
12900 case AIM_GET_BEHIND:
12901 case AIM_AVOID:
12902 case AIM_PATH:
12903 case AIM_NONE:
12904 case AIM_WAYPOINTS:
12905 case AIM_SAFETY:
12906 break;
12907 case AIM_STRAFE:
12908 case AIM_BIGSHIP:
12909 case AIM_DOCK:
12910 case AIM_PLAY_DEAD:
12911 case AIM_EVADE_WEAPON:
12912 case AIM_BAY_EMERGE:
12913 case AIM_BAY_DEPART:
12914 case AIM_SENTRYGUN:
12915 case AIM_WARP_OUT:
12916 case AIM_FLY_TO_SHIP:
12917 return;
12918 default:
12919 Int3(); // Bogus mode!
12920 return;
12921 }
12922
12923 if (is_instructor(&Objects[Ships[aip->shipnum].objnum]))
12924 return; // Instructor doesn't evade.
12925
12926 float t = ai_endangered_by_weapon(aip);
12927 if ((t > 0.0f) && (t < 1.0f)) {
12928 // Check if this weapon is from a large ship Pl_objp is attacking... if so, enter strafe mode
12929 if ( !(aip->ai_flags[AI::AI_Flags::No_dynamic]) ) {
12930 if ( ai_big_maybe_enter_strafe_mode(Pl_objp, aip->danger_weapon_objnum) ) {
12931 return;
12932 }
12933 }
12934
12935 switch (aip->mode) {
12936 case AIM_CHASE:
12937 switch (aip->submode) {
12938 case SM_EVADE:
12939 case SM_ATTACK_FOREVER:
12940 case SM_AVOID:
12941 case SM_GET_AWAY:
12942 case SM_EVADE_WEAPON:
12943 break;
12944 default:
12945 if (ai_near_full_strength(Pl_objp)) {
12946 aip->submode = SM_SUPER_ATTACK;
12947 aip->submode_start_time = Missiontime;
12948 aip->last_attack_time = Missiontime;
12949 } else {
12950 aip->submode = SM_EVADE_WEAPON;
12951 aip->submode_start_time = Missiontime;
12952 }
12953 break;
12954 }
12955 break;
12956 case AIM_GUARD:
12957 case AIM_STILL:
12958 case AIM_STAY_NEAR:
12959 case AIM_EVADE:
12960 case AIM_GET_BEHIND:
12961 case AIM_AVOID:
12962 case AIM_PATH:
12963 case AIM_NONE:
12964 case AIM_WAYPOINTS:
12965 case AIM_FLY_TO_SHIP:
12966 case AIM_SAFETY:
12967 if (!(aip->ai_flags[AI::AI_Flags::No_dynamic, AI::AI_Flags::Kamikaze]) && Ship_info[Ships[aip->shipnum].ship_info_index].is_small_ship()) {
12968 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
12969 aip->previous_mode = aip->mode;
12970 aip->previous_submode = aip->submode;
12971 aip->mode = AIM_EVADE_WEAPON;
12972 aip->submode = -1;
12973 aip->submode_start_time = Missiontime;
12974 aip->mode_time = timestamp(MAX_EVADE_TIME); // Evade for up to five seconds.
12975 }
12976 break;
12977 case AIM_STRAFE:
12978 case AIM_BIGSHIP:
12979 case AIM_DOCK:
12980 case AIM_PLAY_DEAD:
12981 case AIM_EVADE_WEAPON:
12982 case AIM_BAY_EMERGE:
12983 case AIM_BAY_DEPART:
12984 case AIM_SENTRYGUN:
12985 break;
12986 default:
12987 Int3(); // Bogus mode!
12988 }
12989 }
12990 }
12991
12992 // manage the opening and closing of fighter bay related subobject animations
12993 // input: pl_objp => the object which is/has entered/exited the fighter bay
12994 // aip => ai info for said object
12995 // done => true if the object has is finished with the path, or is dead
ai_manage_bay_doors(object * pl_objp,ai_info * aip,bool done)12996 void ai_manage_bay_doors(object *pl_objp, ai_info *aip, bool done)
12997 {
12998 Assert( pl_objp );
12999 Assert( pl_objp->instance >= 0 );
13000 Assert( aip );
13001
13002 ship *shipp = &Ships[pl_objp->instance];
13003
13004 if (done && !shipp->bay_doors_need_open)
13005 return;
13006
13007 if (done)
13008 shipp->bay_doors_need_open = false;
13009
13010 // if this happens then the parent was probably destroyed/departed, so just skip the rest
13011 if (shipp->bay_doors_parent_shipnum < 0)
13012 return;
13013
13014 ship *parent_ship = &Ships[shipp->bay_doors_parent_shipnum];
13015
13016 if (done)
13017 parent_ship->bay_doors_wanting_open--;
13018
13019 // trigger an open if we need it
13020 if ( (parent_ship->bay_doors_status == MA_POS_NOT_SET) && (parent_ship->bay_doors_wanting_open > 0) ) {
13021 if ( model_anim_start_type(parent_ship, AnimationTriggerType::DockBayDoor, shipp->bay_doors_launched_from, 1) ) {
13022 parent_ship->bay_doors_status = MA_POS_SET;
13023 parent_ship->bay_doors_anim_done_time = model_anim_get_time_type(parent_ship, AnimationTriggerType::DockBayDoor, shipp->bay_doors_launched_from);
13024 } else {
13025 parent_ship->bay_doors_status = MA_POS_READY;
13026 }
13027 }
13028
13029 // if we are already open, and no longer need to be, then close the doors
13030 if ( (parent_ship->bay_doors_status == MA_POS_READY) && (parent_ship->bay_doors_wanting_open <= 0) ) {
13031 model_anim_start_type(parent_ship, AnimationTriggerType::DockBayDoor, shipp->bay_doors_launched_from, -1);
13032 parent_ship->bay_doors_status = MA_POS_NOT_SET;
13033 }
13034
13035 if ( (parent_ship->bay_doors_status == MA_POS_SET) && timestamp_elapsed(parent_ship->bay_doors_anim_done_time) ) {
13036 parent_ship->bay_doors_status = MA_POS_READY;
13037 }
13038 }
13039
13040 // determine what path to use when emerging from a fighter bay
13041 // input: pl_objp => pointer to object for ship that is arriving
13042 // pos => output parameter, it is the starting world pos for path choosen
13043 // fvec => output parameter, this is the forward vector that ship has when arriving
13044 //
13045 // exit: -1 => path could not be located
13046 // 0 => success
ai_acquire_emerge_path(object * pl_objp,int parent_objnum,int allowed_path_mask,vec3d * pos,vec3d * fvec)13047 int ai_acquire_emerge_path(object *pl_objp, int parent_objnum, int allowed_path_mask, vec3d *pos, vec3d *fvec)
13048 {
13049 int path_index, bay_path;
13050 pnode *pnp;
13051 vec3d *next_point;
13052
13053 Assertion(pl_objp->instance >= 0, "Arriving ship does not have a valid ship number. Has %d instead. Please report!", pl_objp->instance);
13054 ship *shipp = &Ships[pl_objp->instance];
13055 Assertion(shipp->ai_index >= 0, "Arriving ship does not have a valid ai index. Has %d instead. Please report!", shipp->ai_index);
13056 ai_info *aip = &Ai_info[shipp->ai_index];
13057
13058 if ( parent_objnum == -1 ) {
13059 Int3();
13060 return -1;
13061 }
13062
13063 Assertion(parent_objnum >= 0, "Arriving ship does not have a parent object number. Has %d instead. Please report!", parent_objnum);
13064 object *parent_objp = &Objects[parent_objnum];
13065 ship *parent_shipp = &Ships[parent_objp->instance];
13066
13067 polymodel *pm = model_get( Ship_info[parent_shipp->ship_info_index].model_num );
13068 ship_bay *bay = pm->ship_bay;
13069
13070 if ( bay == nullptr ) {
13071 Warning(LOCATION, "Ship %s was set to arrive from fighter bay on object %s, but no fighter bay exists on that ships' model (%s).\n", shipp->ship_name, parent_shipp->ship_name, pm->filename);
13072 return -1;
13073 }
13074
13075 if ( bay->num_paths <= 0 ) {
13076 Warning(LOCATION, "Ship %s was set to arrive from fighter bay on object %s, but no fighter bay paths exist on that ships' model (%s).\n", shipp->ship_name, parent_shipp->ship_name, pm->filename);
13077 return -1;
13078 }
13079
13080 // try to find a bay path that is not taken
13081 // Goober5000 - choose from among allowed paths
13082 if (allowed_path_mask != 0)
13083 {
13084 int i, num_allowed_paths = 0, allowed_bay_paths[MAX_SHIP_BAY_PATHS];
13085
13086 memset(allowed_bay_paths, 0, sizeof(allowed_bay_paths));
13087
13088 for (i = 0; i < bay->num_paths; i++)
13089 {
13090 if (allowed_path_mask & (1 << i))
13091 {
13092 allowed_bay_paths[num_allowed_paths] = i;
13093 num_allowed_paths++;
13094 }
13095 }
13096
13097 // cycle through the allowed paths
13098 bay_path = allowed_bay_paths[Ai_last_arrive_path % num_allowed_paths];
13099 }
13100 // choose from among all paths
13101 else
13102 {
13103 bay_path = Ai_last_arrive_path % bay->num_paths;
13104 }
13105
13106 Ai_last_arrive_path++;
13107
13108 path_index = bay->path_indexes[bay_path];
13109
13110 if ( path_index == -1 )
13111 return -1;
13112
13113 // notify parent that I'll want to leave
13114 parent_shipp->bay_doors_wanting_open++;
13115
13116 // keep track of my own status as well
13117 shipp->bay_doors_need_open = true;
13118 shipp->bay_doors_launched_from = (ubyte)bay_path;
13119 shipp->bay_doors_parent_shipnum = parent_objp->instance;
13120
13121 // create the path for pl_objp to follow
13122 create_model_exit_path(pl_objp, parent_objp, path_index, pm->paths[path_index].nverts);
13123
13124 // now return to the caller what the starting world pos and starting fvec for the ship will be
13125 Assert((aip->path_start >= 0) && (aip->path_start < MAX_PATH_POINTS));
13126 pnp = &Path_points[aip->path_start];
13127 *pos = pnp->pos;
13128
13129 // calc the forward vector using the starting two points of the path
13130 pnp = &Path_points[aip->path_start+1];
13131 next_point = &pnp->pos;
13132 vm_vec_normalized_dir(fvec, next_point, pos);
13133
13134 // record the parent objnum, since we'll need it once we're done with following the path
13135 aip->goal_objnum = parent_objnum;
13136 aip->goal_signature = parent_objp->signature;
13137 aip->mode = AIM_BAY_EMERGE;
13138 aip->submode_start_time = Missiontime;
13139
13140 //due to the door animations the ship has to remain still until the doors are open
13141 vec3d vel = ZERO_VECTOR;
13142 pl_objp->phys_info.vel = vel;
13143 pl_objp->phys_info.desired_vel = vel;
13144 pl_objp->phys_info.prev_ramp_vel.xyz.x = 0.0f;
13145 pl_objp->phys_info.prev_ramp_vel.xyz.y = 0.0f;
13146 pl_objp->phys_info.prev_ramp_vel.xyz.z = 0.0f;
13147 pl_objp->phys_info.forward_thrust = 0.0f; // How much the forward thruster is applied. 0-1.
13148
13149 return 0;
13150 }
13151
13152 /**
13153 * Clean up path data used for emerging from a fighter bay
13154 */
ai_emerge_bay_path_cleanup(ai_info * aip)13155 void ai_emerge_bay_path_cleanup(ai_info *aip)
13156 {
13157 aip->path_start = -1;
13158 aip->path_cur = -1;
13159 aip->path_length = 0;
13160 aip->mode = AIM_NONE;
13161 }
13162
13163 /**
13164 * Handler for AIM_BAY_EMERGE
13165 */
ai_bay_emerge()13166 void ai_bay_emerge()
13167 {
13168 ai_info *aip;
13169 ship *shipp;
13170 int parent_died=0;
13171
13172 Assert( Pl_objp != NULL );
13173 Assert( Pl_objp->instance >= 0 );
13174
13175 shipp = &Ships[Pl_objp->instance];
13176 aip = &Ai_info[shipp->ai_index];
13177
13178 // if no path to follow, leave this mode
13179 if ( aip->path_start < 0 ) {
13180 ai_manage_bay_doors(Pl_objp, aip, true);
13181 aip->mode = AIM_NONE;
13182 return;
13183 }
13184
13185 // ensure parent ship is still alive
13186 if (aip->goal_objnum < 0) {
13187 parent_died = 1;
13188 }
13189
13190 if ( !parent_died ) {
13191 if ( Objects[aip->goal_objnum].signature != aip->goal_signature ) {
13192 parent_died = 1;
13193 }
13194 }
13195
13196 if ( !parent_died ) {
13197 Assert(Objects[aip->goal_objnum].type == OBJ_SHIP);
13198 if ( Ships[Objects[aip->goal_objnum].instance].flags[Ship::Ship_Flags::Dying] ) {
13199 parent_died = 1;
13200 }
13201 }
13202
13203 if ( parent_died ) {
13204 ai_emerge_bay_path_cleanup(aip);
13205 return;
13206 }
13207
13208 ai_manage_bay_doors(Pl_objp, aip, false);
13209
13210 if ( Ships[Objects[aip->goal_objnum].instance].bay_doors_status != MA_POS_READY )
13211 return;
13212
13213 // follow the path to the final point
13214 ai_path();
13215
13216 // New test: must have been in AI_EMERGE mode for at least 10 seconds, and be a minimum distance from the start point
13217 if ( ( (Missiontime - aip->submode_start_time) > 10*F1_0 ) && (vm_vec_dist_quick(&Pl_objp->pos, &Objects[aip->goal_objnum].pos) > 0.75f * Objects[aip->goal_objnum].radius)) {
13218 // erase path
13219 ai_emerge_bay_path_cleanup(aip);
13220 // deal with model animations
13221 ai_manage_bay_doors(Pl_objp, aip, true);
13222 }
13223
13224 // 2-25-99: Need this check to fix an assert for supercap ships... maybe we'll only do this check for supercaps
13225 if (aip->path_cur > (aip->path_start+aip->path_length-1)) {
13226 ai_emerge_bay_path_cleanup(aip);
13227 // deal with model animations
13228 ai_manage_bay_doors(Pl_objp, aip, true);
13229 }
13230 }
13231
13232 // Select the closest depart path
13233 //
13234 // input: aip => ai info pointer to ship seeking to depart
13235 // pm => pointer to polymodel for the ship contining the ship bay/depart paths
13236 //
13237 // exit: >=0 => ship bay path index for depart path (ie index into sb->paths[])
13238 // -1 => no path could be found
13239 //
13240 // NOTE: this function should only be used for calculating closest depart paths for ai mode
13241 // AI_BAY_DEPART. It tries to find the closest path that isn't already in use
ai_find_closest_depart_path(ai_info * aip,polymodel * pm,int allowed_path_mask)13242 int ai_find_closest_depart_path(ai_info *aip, polymodel *pm, int allowed_path_mask)
13243 {
13244 int i, j, best_path, best_free_path;
13245 float dist, min_dist, min_free_dist;
13246 vec3d *source;
13247 model_path *mp;
13248 ship_bay *bay;
13249
13250 bay = pm->ship_bay;
13251
13252 best_free_path = best_path = -1;
13253 min_free_dist = min_dist = 1e20f;
13254 Assert(aip->shipnum >= 0);
13255 source = &Objects[Ships[aip->shipnum].objnum].pos;
13256
13257 for ( i = 0; i < bay->num_paths; i++ )
13258 {
13259 // Goober5000 - maybe skip this path
13260 if ((allowed_path_mask != 0) && !(allowed_path_mask & (1 << i)))
13261 continue;
13262
13263 mp = &pm->paths[bay->path_indexes[i]];
13264 for ( j = 0; j < mp->nverts; j++ )
13265 {
13266 dist = vm_vec_dist_squared(source, &mp->verts[j].pos);
13267
13268 if (dist < min_dist)
13269 {
13270 min_dist = dist;
13271 best_path = i;
13272 }
13273
13274 // If this is a free path
13275 if (!(bay->depart_flags & (1 << i)))
13276 {
13277 if (dist < min_free_dist)
13278 {
13279 min_free_dist = dist;
13280 best_free_path = i;
13281 }
13282 }
13283 }
13284 }
13285
13286 if (best_free_path >= 0)
13287 {
13288 return best_free_path;
13289 }
13290
13291 return best_path;
13292 }
13293
13294 // determine what path to use when trying to depart to a fighter bay
13295 // NOTE: this should be called when AIM_BAY_DEPART mode is set
13296 //
13297 // input: pl_objp => pointer to object for ship that is departing
13298 //
13299 // exit: -1 => could not find depart path
13300 // 0 => found depart path
ai_acquire_depart_path(object * pl_objp,int parent_objnum,int allowed_path_mask)13301 int ai_acquire_depart_path(object *pl_objp, int parent_objnum, int allowed_path_mask)
13302 {
13303 int path_index = -1;
13304 int ship_bay_path = -1;
13305
13306 ship *shipp = &Ships[pl_objp->instance];
13307 ai_info *aip = &Ai_info[shipp->ai_index];
13308
13309 if ( parent_objnum < 0 )
13310 {
13311 Warning(LOCATION, "In ai_acquire_depart_path(), specified a negative object number for the parent ship! (Departing ship is %s.) Looking for another ship...", shipp->ship_name);
13312
13313 // try to locate a capital ship on the same team:
13314 int shipnum = ship_get_ship_for_departure(shipp->team);
13315
13316 if (shipnum >= 0)
13317 parent_objnum = Ships[shipnum].objnum;
13318 }
13319
13320 aip->path_start = -1;
13321
13322 if ( parent_objnum < 0 )
13323 return -1;
13324
13325 object *parent_objp = &Objects[parent_objnum];
13326 polymodel *pm = model_get(Ship_info[Ships[parent_objp->instance].ship_info_index].model_num );
13327 ship_bay *bay = pm->ship_bay;
13328
13329 if ( bay == NULL )
13330 return -1;
13331 if ( bay->num_paths <= 0 )
13332 return -1;
13333
13334 // take the closest path we can find
13335 ship_bay_path = ai_find_closest_depart_path(aip, pm, allowed_path_mask);
13336
13337 if ( ship_bay_path < 0 )
13338 return -1;
13339
13340 path_index = bay->path_indexes[ship_bay_path];
13341 aip->submode_parm0 = ship_bay_path;
13342 bay->depart_flags |= (1<<ship_bay_path);
13343
13344 if ( path_index == -1 ) {
13345 return -1;
13346 }
13347
13348 // notify parent that I'll want to enter bay
13349 Ships[parent_objp->instance].bay_doors_wanting_open++;
13350
13351 // keep track of my own status as well
13352 shipp->bay_doors_need_open = true;
13353 shipp->bay_doors_launched_from = (ubyte)ship_bay_path;
13354 shipp->bay_doors_parent_shipnum = parent_objp->instance;
13355
13356 Assert(path_index < pm->n_paths);
13357 ai_find_path(pl_objp, parent_objnum, path_index, 0);
13358
13359 // Set this flag, so we don't bother recreating the path... we won't need to update the path
13360 // that has just been created.
13361 aip->ai_flags.remove(AI::AI_Flags::Use_static_path);
13362
13363 aip->goal_objnum = parent_objnum;
13364 aip->goal_signature = parent_objp->signature;
13365 aip->mode = AIM_BAY_DEPART;
13366
13367 shipp->flags.set(Ship::Ship_Flags::Depart_dockbay);
13368 return 0;
13369 }
13370
13371 /**
13372 * Handler for AIM_BAY_DEPART
13373 */
ai_bay_depart()13374 void ai_bay_depart()
13375 {
13376 ai_info *aip;
13377 int anchor_shipnum;
13378
13379 aip = &Ai_info[Ships[Pl_objp->instance].ai_index];
13380
13381 // if no path to follow, leave this mode
13382 if ( aip->path_start < 0 ) {
13383 aip->mode = AIM_NONE;
13384 return;
13385 }
13386
13387 // check if parent ship valid; if not, abort depart
13388 anchor_shipnum = ship_name_lookup(Parse_names[Ships[Pl_objp->instance].departure_anchor]);
13389 if (anchor_shipnum < 0 || !ship_useful_for_departure(anchor_shipnum, Ships[Pl_objp->instance].departure_path_mask))
13390 {
13391 mprintf(("Aborting bay departure!\n"));
13392 aip->mode = AIM_NONE;
13393
13394 Ships[Pl_objp->instance].flags.remove(Ship::Ship_Flags::Depart_dockbay);
13395 return;
13396 }
13397
13398 ai_manage_bay_doors(Pl_objp, aip, false);
13399
13400 if ( Ships[anchor_shipnum].bay_doors_status != MA_POS_READY )
13401 return;
13402
13403 // follow the path to the final point
13404 ai_path();
13405
13406 //if we are on the last segment of the path and the bay doors are not open, open them
13407 //if we are on any other segment of the path, close them
13408
13409 // if the final point is reached, let default AI take over
13410 if ( aip->path_cur >= (aip->path_start+aip->path_length) ) {
13411 ai_manage_bay_doors(Pl_objp, aip, true);
13412
13413 // Volition bay code
13414 polymodel *pm;
13415 ship_bay *bay;
13416
13417 pm = model_get(Ship_info[Ships[Objects[aip->goal_objnum].instance].ship_info_index].model_num);
13418 bay = pm->ship_bay;
13419 if ( bay != NULL ) {
13420 bay->depart_flags &= ~(1<<aip->submode_parm0);
13421 }
13422
13423 // make ship disappear
13424 ship_actually_depart(Pl_objp->instance, SHIP_DEPARTED_BAY);
13425
13426 // clean up path stuff
13427 aip->path_start = -1;
13428 aip->path_cur = -1;
13429 aip->path_length = 0;
13430 aip->mode = AIM_NONE;
13431 }
13432 }
13433
13434 /**
13435 * Handler for AIM_SENTRYGUN. This AI mode is for sentry guns only (ie floating turrets).
13436 */
ai_sentrygun()13437 void ai_sentrygun()
13438 {
13439 // Nothing to do here. Turret firing is handled via process_subobjects().
13440 // If you want the sentry guns to do anything beyond firing their turrets at enemies, add it here!
13441 }
13442
13443 /**
13444 * Execute behavior given by aip->mode.
13445 * @todo Complete the AIM_GET_BEHIND option
13446 */
ai_execute_behavior(ai_info * aip)13447 void ai_execute_behavior(ai_info *aip)
13448 {
13449 switch (aip->mode) {
13450 case AIM_CHASE:
13451 if (En_objp) {
13452 ai_chase();
13453 } else if (aip->submode == SM_EVADE_WEAPON) {
13454 evade_weapon();
13455 // maybe reset submode
13456 if (aip->danger_weapon_objnum == -1) {
13457 aip->submode = SM_ATTACK;
13458 aip->submode_start_time = Missiontime;
13459 aip->last_attack_time = Missiontime;
13460 }
13461 } else {
13462 // Don't circle if this is the instructor.
13463 ship *shipp = &Ships[aip->shipnum];
13464 ship_info *sip = &Ship_info[shipp->ship_info_index];
13465
13466 if (strnicmp(shipp->ship_name, INSTRUCTOR_SHIP_NAME, strlen(INSTRUCTOR_SHIP_NAME)) != 0) {
13467 if (sip->is_big_or_huge()) {
13468 aip->mode = AIM_NONE;
13469 } else {
13470 ai_chase_circle(Pl_objp);
13471 }
13472 }
13473 }
13474 break;
13475 case AIM_EVADE:
13476 if (En_objp) {
13477 ai_evade();
13478 } else {
13479 vec3d tvec;
13480 vm_vec_scale_add(&tvec, &Pl_objp->pos, &Pl_objp->orient.vec.rvec, 100.0f);
13481 turn_towards_point(Pl_objp, &tvec, NULL, 0.0f);
13482 accelerate_ship(aip, 0.5f);
13483 }
13484 break;
13485 case AIM_STILL:
13486 ai_still();
13487 break;
13488 case AIM_STAY_NEAR:
13489 ai_stay_near();
13490 break;
13491 case AIM_GUARD:
13492 ai_guard();
13493 break;
13494 case AIM_WAYPOINTS:
13495 ai_waypoints();
13496 break;
13497 case AIM_FLY_TO_SHIP:
13498 ai_fly_to_ship();
13499 break;
13500 case AIM_DOCK:
13501 ai_dock();
13502 break;
13503 case AIM_NONE:
13504 break;
13505 case AIM_BIGSHIP:
13506 break;
13507 case AIM_PATH: {
13508 int path_num;
13509 path_num = ai_return_path_num_from_dockbay(&Objects[aip->goal_objnum], 0);
13510 ai_find_path(Pl_objp, aip->goal_objnum, path_num, 0);
13511 ai_path();
13512 break;
13513 }
13514 case AIM_SAFETY:
13515 ai_safety();
13516 break;
13517 case AIM_EVADE_WEAPON:
13518 evade_weapon();
13519 break;
13520 case AIM_STRAFE:
13521 if (En_objp) {
13522 Assert(En_objp->type == OBJ_SHIP);
13523 ai_big_strafe(); // strafe a big ship
13524 } else {
13525 aip->mode = AIM_NONE;
13526 }
13527 break;
13528 case AIM_BAY_EMERGE:
13529 ai_bay_emerge();
13530 break;
13531 case AIM_BAY_DEPART:
13532 ai_bay_depart();
13533 break;
13534 case AIM_SENTRYGUN:
13535 ai_sentrygun();
13536 break;
13537 case AIM_WARP_OUT:
13538 break; // Note, handled directly from ai_frame().
13539 case AIM_GET_BEHIND:
13540 // FIXME: got this from TBP and added it here to skip the Int3() but don't really want to handle it
13541 // properly until after 3.6.7 just to avoid delaying release or breaking something - taylor
13542 break;
13543 default:
13544 Int3(); // This should never happen -- MK, 5/12/97
13545 break;
13546 }
13547
13548 if ( Ship_info[Ships[aip->shipnum].ship_info_index].is_flyable() ) {
13549 maybe_evade_dumbfire_weapon(aip);
13550 }
13551 }
13552
13553 // Auxiliary function for maybe_request_support.
13554 // Return 1 if subsystem "type" is worthy of repair, else return 0.
13555 // Since subsystems cannot be repaired if they are at 0 strength, don't return 1 if subsystem is dead.
mrs_subsystem(ship * shipp,int type)13556 int mrs_subsystem(ship *shipp, int type)
13557 {
13558 float t;
13559
13560 t = ship_get_subsystem_strength(shipp, type);
13561
13562 if (t > 0.0f) {
13563 return (int) ((1.0f - t) * 3);
13564 } else {
13565 return 3;
13566 }
13567 }
13568
13569 /**
13570 * Return number of ships on *objp's team that are currently rearming.
13571 */
num_allies_rearming(object * objp)13572 int num_allies_rearming(object *objp)
13573 {
13574 ship_obj *so;
13575 int team;
13576 int count = 0;
13577
13578 team = Ships[objp->instance].team;
13579
13580 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
13581 object *A;
13582
13583 Assert (so->objnum != -1);
13584 A = &Objects[so->objnum];
13585
13586 if (Ships[A->instance].team == team) {
13587 if (Ai_info[Ships[A->instance].ai_index].ai_flags[AI::AI_Flags::Repairing, AI::AI_Flags::Awaiting_repair]) {
13588 count++;
13589 }
13590 }
13591 }
13592
13593 return count;
13594 }
13595
13596
13597 // Maybe ship *objp should request support (rearm/repair).
13598 // If it does, return TRUE, else return FALSE.
maybe_request_support(object * objp)13599 int maybe_request_support(object *objp)
13600 {
13601 ship_info *sip;
13602 ship *shipp;
13603 ai_info *aip;
13604 weapon_info *wip;
13605 int desire;
13606 int i;
13607 float r;
13608
13609 Assert(objp->type == OBJ_SHIP);
13610 shipp = &Ships[objp->instance];
13611 aip = &Ai_info[shipp->ai_index];
13612 sip = &Ship_info[shipp->ship_info_index];
13613
13614 // Only fighters and bombers request support.
13615 if (!(sip->is_fighter_bomber()))
13616 return 0;
13617
13618 // A ship that is currently awaiting does not need support!
13619 if (aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair])
13620 return 0;
13621
13622 if (!timestamp_elapsed(aip->next_rearm_request_timestamp))
13623 return 0;
13624
13625 // just do a simple check first, and the more expensive check later
13626 if (!is_support_allowed(objp, true))
13627 return 0;
13628
13629 // Goober5000 - a ship that is currently docked shouldn't request repair.
13630 // This is to prevent support ships being summoned for a ship that is getting
13631 // towed away by other means.
13632 if (object_is_docked(objp))
13633 return 0;
13634
13635 // Compute a desire value.
13636 // Desire of 0 means no reason to request support.
13637 // 1 is slight, 2 more, etc. Maximum is around 20. Anything larger than 3 is pretty strong.
13638 desire = 0;
13639
13640 // Set desire based on hull strength.
13641 // Note: We no longer repair hull, so this would cause repeated repair requests.
13642 // Added back in upon mission flag condition - Goober5000
13643 if (The_mission.flags[Mission::Mission_Flags::Support_repairs_hull])
13644 {
13645 desire += 6 - (int) (get_hull_pct(objp) * 6.0f);
13646 }
13647
13648 // Set desire based on key subsystems.
13649 desire += 2*mrs_subsystem(shipp, SUBSYSTEM_ENGINE); // Note, disabled engine forces repair request, regardless of nearby enemies.
13650 desire += mrs_subsystem(shipp, SUBSYSTEM_COMMUNICATION);
13651 desire += mrs_subsystem(shipp, SUBSYSTEM_WEAPONS);
13652 desire += mrs_subsystem(shipp, SUBSYSTEM_SENSORS);
13653
13654
13655 ship_weapon *swp = &shipp->weapons;
13656
13657 // Set desire based on percentage of secondary weapons.
13658 for (i = 0; i < swp->num_secondary_banks; ++i)
13659 {
13660 if (swp->secondary_bank_start_ammo[i] > 0)
13661 {
13662 r = (float)swp->secondary_bank_ammo[i] / swp->secondary_bank_start_ammo[i];
13663 desire += (int)((1.0f - r) * 3.0f);
13664 }
13665 }
13666
13667 // Set desire based on ballistic weapons - Goober5000
13668 for (i = 0; i < swp->num_primary_banks; ++i)
13669 {
13670 wip = &Weapon_info[swp->primary_bank_weapons[i]];
13671
13672 if (wip->wi_flags[Weapon::Info_Flags::Ballistic] && swp->primary_bank_start_ammo[i] > 0)
13673 {
13674 r = (float) swp->primary_bank_ammo[i] / swp->primary_bank_start_ammo[i];
13675
13676 // cube ammo level for better behavior, and adjust for number of banks
13677 desire += (int) ((1.0f - r)*(1.0f - r)*(1.0f - r) * (5.0f / swp->num_primary_banks));
13678 }
13679 }
13680
13681
13682 // If no reason to repair, don't bother to see if it's safe to repair.
13683 if (desire == 0){
13684 return 0;
13685 }
13686
13687 bool try_to_rearm = false;
13688
13689 // Compute danger threshold.
13690 // Balance this with desire and maybe request support.
13691 if (ai_good_time_to_rearm( objp )) {
13692 try_to_rearm = true;
13693 } else if (num_allies_rearming(objp) < 2) {
13694 if (desire >= 8) { // guarantees disabled will cause repair request
13695 try_to_rearm = true;
13696 } else if (desire >= 3) { // >= 3 means having a single subsystem fully blown will cause repair.
13697 int count;
13698 int objnum = find_nearby_threat(OBJ_INDEX(objp), iff_get_attacker_mask(obj_team(objp)), 2000.0f, &count);
13699
13700 if ((objnum == -1) || (count < 2) || (vm_vec_dist_quick(&objp->pos, &Objects[objnum].pos) > 3000.0f*count/desire)) {
13701 try_to_rearm = true;
13702 }
13703 }
13704 }
13705
13706 if (try_to_rearm) {
13707 // Now do the more thorough check
13708 if (is_support_allowed(objp)) {
13709 ai_issue_rearm_request(objp);
13710 return 1;
13711 }
13712 }
13713
13714 return 0;
13715 }
13716
ai_set_mode_warp_out(object * objp,ai_info * aip)13717 void ai_set_mode_warp_out(object *objp, ai_info *aip)
13718 {
13719 ai_abort_rearm_request(objp);
13720 if (aip->mode != AIM_WARP_OUT) {
13721 aip->mode = AIM_WARP_OUT;
13722 aip->submode = AIS_WARP_1;
13723 aip->submode_start_time = Missiontime;
13724 }
13725 }
13726
13727 // Maybe make ship depart (Goober5000 - changed from always warp ship out)
13728 // Shivan and HoL fighter/bomber warp out if their weapons subsystems have been destroyed.
ai_maybe_depart(object * objp)13729 void ai_maybe_depart(object *objp)
13730 {
13731 ship *shipp;
13732 ship_info *sip;
13733
13734 // don't do anything if in a training mission.
13735 if ( The_mission.game_type & MISSION_TYPE_TRAINING )
13736 return;
13737
13738 Assert(objp->type == OBJ_SHIP);
13739
13740 shipp = &Ships[objp->instance];
13741 ai_info *aip = &Ai_info[shipp->ai_index];
13742 sip = &Ship_info[shipp->ship_info_index];
13743
13744 if (aip->mode == AIM_WARP_OUT || aip->mode == AIM_BAY_DEPART)
13745 return;
13746
13747 // If a support ship with no goals and low hull, depart. Be sure that there are no pending goals
13748 // in the support ships ai_goal array. Just process this ships goals.
13749 if (sip->flags[Ship::Info_Flags::Support]) {
13750 if ( timestamp_elapsed(aip->warp_out_timestamp) ) {
13751 ai_process_mission_orders( OBJ_INDEX(objp), aip );
13752 if ( (aip->support_ship_objnum == -1) && (get_hull_pct(objp) < 0.25f) ) {
13753 if (!shipp->is_departing()) {
13754 if (!mission_do_departure(objp)) {
13755 // if departure failed, try again at a later point
13756 // (timestamp taken from ai_do_objects_repairing_stuff)
13757 aip->warp_out_timestamp = timestamp((int) ((30 + 10*frand()) * 1000));
13758 }
13759 }
13760 }
13761 }
13762 }
13763
13764 // some iffs don't warp out, they'll eventually request support.
13765 if (Iff_info[shipp->team].flags & IFFF_SUPPORT_ALLOWED)
13766 return;
13767
13768 if (!shipp->is_departing()) {
13769 if (sip->is_fighter_bomber()) {
13770 if (aip->warp_out_timestamp == 0) {
13771 //if (ship_get_subsystem_strength(shipp, SUBSYSTEM_WEAPONS) == 0.0f) {
13772 // aip->warp_out_timestamp = timestamp(Random::next(10, 19) * 1000);
13773 //}
13774 } else if (timestamp_elapsed(aip->warp_out_timestamp)) {
13775 mission_do_departure(objp);
13776 }
13777 }
13778 }
13779 }
13780
13781 /**
13782 * Warp this ship out.
13783 */
ai_warp_out(object * objp)13784 void ai_warp_out(object *objp)
13785 {
13786 ship *shipp = &Ships[objp->instance];
13787 ai_info *aip = &Ai_info[shipp->ai_index];
13788
13789 // if dying, don't warp out.
13790 if (shipp->flags[Ship::Ship_Flags::Dying])
13791 return;
13792
13793 // Goober5000 - check for engine or navigation failure
13794 if (!ship_engine_ok_to_warp(shipp) || !ship_navigation_ok_to_warp(shipp))
13795 {
13796 // you shouldn't hit this... if you do, then I need to add a check for it
13797 // in whatever function initiates a warpout
13798 Assert (!(shipp->flags[Ship::Ship_Flags::No_subspace_drive]));
13799
13800 // flag us as trying to warp so that this function keeps getting called
13801 // (in other words, if we can't warp just yet, we want to warp at the first
13802 // opportunity)
13803 if (aip->mode != AIM_WARP_OUT)
13804 aip->mode = AIM_WARP_OUT;
13805 aip->submode = AIS_WARP_1;
13806 aip->ai_flags.set(AI::AI_Flags::Trying_unsuccessfully_to_warp);
13807
13808 return;
13809 }
13810
13811 // Goober5000 - make sure the flag is clear (if it was previously set)
13812 aip->ai_flags.remove(AI::AI_Flags::Trying_unsuccessfully_to_warp);
13813
13814 switch (aip->submode) {
13815 case AIS_WARP_1:
13816 aip->force_warp_time = timestamp(10*1000); // Try to avoid a collision for up to ten seconds.
13817 aip->submode = AIS_WARP_2;
13818 aip->submode_start_time = Missiontime;
13819 break;
13820 case AIS_WARP_2: // Make sure won't collide with any object.
13821 if (timestamp_elapsed(aip->force_warp_time)
13822 || (!collide_predict_large_ship(objp, objp->radius*2.0f + 100.0f)
13823 || (Warp_params[shipp->warpout_params_index].warp_type == WT_HYPERSPACE
13824 && !collide_predict_large_ship(objp, 100000.0f)))) {
13825 aip->submode = AIS_WARP_3;
13826 aip->submode_start_time = Missiontime;
13827
13828 // maybe recalculate collision pairs.
13829 if ((objp->flags[Object::Object_Flags::Collides]) && (ship_get_warpout_speed(objp) > ship_get_max_speed(shipp))) {
13830 // recalculate collision pairs
13831 OBJ_RECALC_PAIRS(objp);
13832 }
13833
13834 aip->force_warp_time = timestamp(4*1000); // Try to attain target speed for up to 4 seconds.
13835 } else {
13836 vec3d goal_point;
13837 vm_vec_scale_add(&goal_point, &objp->pos, &objp->orient.vec.uvec, 100.0f);
13838 turn_towards_point(objp, &goal_point, NULL, 0.0f);
13839 accelerate_ship(aip, 0.0f);
13840 }
13841 break;
13842 case AIS_WARP_3:
13843 // Rampup desired_vel in here from current to desired velocity and set PF_USE_VEL. (not sure this is the right flag)
13844 // See shipfx#572 for sample code.
13845 float speed, goal_speed;
13846 goal_speed = ship_get_warpout_speed(objp);
13847
13848 // HUGE ships go immediately to AIS_WARP_4
13849 if (Ship_info[shipp->ship_info_index].is_huge_ship()) {
13850 aip->submode = AIS_WARP_4;
13851 aip->submode_start_time = Missiontime;
13852 break;
13853 }
13854
13855 speed = goal_speed * flFrametime + objp->phys_info.speed * (1.0f - flFrametime);
13856 vm_vec_copy_scale(&objp->phys_info.vel, &objp->orient.vec.fvec, speed);
13857 objp->phys_info.desired_vel = objp->phys_info.vel;
13858
13859 if (timestamp_elapsed(aip->force_warp_time) || (fl_abs(objp->phys_info.speed - goal_speed) < 2.0f)) {
13860 aip->submode = AIS_WARP_4;
13861 aip->submode_start_time = Missiontime;
13862 }
13863 break;
13864 case AIS_WARP_4: {
13865 // Only lets the ship warp after waiting for the warpout engage time
13866 if ( (Missiontime / 100) >= (aip->submode_start_time / 100 + Warp_params[shipp->warpout_params_index].warpout_engage_time) ) {
13867 shipfx_warpout_start(objp);
13868 aip->submode = AIS_WARP_5;
13869 aip->submode_start_time = Missiontime;
13870 }
13871 break;
13872 }
13873 case AIS_WARP_5:
13874 break;
13875 default:
13876 Int3(); // Illegal submode for warping out.
13877 }
13878 }
13879
13880 // Return object index of weapon that could produce a shockwave that should be known about to *objp.
13881 // Return nearest one.
ai_find_shockwave_weapon(const object * objp)13882 static int ai_find_shockwave_weapon(const object *objp)
13883 {
13884 missile_obj *mo;
13885 float nearest_dist = 999999.9f;
13886 int nearest_index = -1;
13887
13888 for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
13889 object *A;
13890 weapon *wp;
13891 weapon_info *wip;
13892
13893 Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
13894 A = &Objects[mo->objnum];
13895
13896 Assert(A->type == OBJ_WEAPON);
13897 Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS));
13898 wp = &Weapons[A->instance];
13899 wip = &Weapon_info[wp->weapon_info_index];
13900 Assert( wip->subtype == WP_MISSILE );
13901
13902 if (wip->shockwave.speed > 0.0f) {
13903 float dist;
13904
13905 dist = vm_vec_dist_quick(&objp->pos, &A->pos);
13906 if (dist < nearest_dist) {
13907 nearest_dist = dist;
13908 nearest_index = mo->objnum;
13909 }
13910 }
13911 }
13912
13913 return nearest_index;
13914
13915 }
13916
13917 #define EVADE_SHOCKWAVE_DAMAGE_THRESHOLD 100.0f
13918
13919 // Tell all ships to avoid a big ship that is blowing up.
13920 // Only avoid if shockwave is fairly large.
13921 // OK to tell everyone to avoid. If they're too far away, that gets cleaned up in the frame interval.
ai_announce_ship_dying(object * dying_objp)13922 void ai_announce_ship_dying(object *dying_objp)
13923 {
13924 float damage = ship_get_exp_damage(dying_objp);
13925 if (damage >= EVADE_SHOCKWAVE_DAMAGE_THRESHOLD) {
13926 ship_obj *so;
13927
13928 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
13929 object *A = &Objects[so->objnum];
13930 Assert(A->type == OBJ_SHIP);
13931
13932 // Goober5000 - the disallow is now handled uniformly in the ai_avoid_shockwave function
13933 /*if (Ship_info[Ships[A->instance].ship_info_index].flags & (SIF_SMALL_SHIP | SIF_FREIGHTER | SIF_TRANSPORT))*/ {
13934 ai_info *aip = &Ai_info[Ships[A->instance].ai_index];
13935
13936 // AL 1-5-98: only avoid shockwave if not docked or repairing
13937 if ( !object_is_docked(A) && !(aip->ai_flags[AI::AI_Flags::Repairing, AI::AI_Flags::Being_repaired]) ) {
13938 aip->ai_flags.set(AI::AI_Flags::Avoid_shockwave_ship);
13939 }
13940 }
13941 }
13942 }
13943 }
13944
13945
13946 // Return object index of weapon that could produce a shockwave that should be known about to *objp.
13947 // Return nearest one.
ai_find_shockwave_ship(object * objp)13948 static int ai_find_shockwave_ship(object *objp)
13949 {
13950 ship_obj *so;
13951 float nearest_dist = 999999.9f;
13952 int nearest_index = -1;
13953
13954 for ( so = GET_NEXT(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
13955 object *A;
13956 ship *shipp;
13957
13958 Assert(so->objnum >= 0 && so->objnum < MAX_OBJECTS);
13959 A = &Objects[so->objnum];
13960
13961 Assert(A->type == OBJ_SHIP);
13962 Assert((A->instance >= 0) && (A->instance < MAX_SHIPS));
13963 shipp = &Ships[A->instance];
13964 // Only look at objects in the process of dying.
13965 if (shipp->flags[Ship::Ship_Flags::Dying]) {
13966 float damage = ship_get_exp_damage(objp);
13967
13968 if (damage >= EVADE_SHOCKWAVE_DAMAGE_THRESHOLD) { // Only evade quite large blasts
13969 float dist;
13970
13971 dist = vm_vec_dist_quick(&objp->pos, &A->pos);
13972 if (dist < nearest_dist) {
13973 nearest_dist = dist;
13974 nearest_index = so->objnum;
13975 }
13976 }
13977 }
13978 }
13979
13980 return nearest_index;
13981
13982 }
13983
aas_1(object * objp,ai_info * aip,vec3d * safe_pos)13984 int aas_1(object *objp, ai_info *aip, vec3d *safe_pos)
13985 {
13986 // MAKE SURE safe_pos DOES NOT TAKE US TOWARD THE A SHIP WE'RE ATTACKING.
13987 if (aip->ai_flags[AI::AI_Flags::Avoid_shockwave_weapon]) {
13988 // If we don't currently know of a weapon to avoid, try to find one.
13989 // If we can't find one, then clear the bit so we don't keep coming here.
13990 if (aip->shockwave_object == -1) {
13991 int shockwave_weapon = ai_find_shockwave_weapon(objp);
13992 if (shockwave_weapon == -1) {
13993 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_weapon);
13994 return 0;
13995 } else {
13996 aip->shockwave_object = shockwave_weapon;
13997 }
13998 }
13999
14000 // OK, we have reason to believe we should avoid aip->shockwave_object.
14001 Assert(aip->shockwave_object > -1);
14002 object *weapon_objp = &Objects[aip->shockwave_object];
14003 if (weapon_objp->type != OBJ_WEAPON) {
14004 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_weapon);
14005 aip->shockwave_object = -1;
14006 return 0;
14007 }
14008
14009 weapon *weaponp = &Weapons[weapon_objp->instance];
14010 weapon_info *wip = &Weapon_info[weaponp->weapon_info_index];
14011 object *target_ship_obj = NULL;
14012
14013 if (wip->shockwave.speed == 0.0f) {
14014 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_weapon);
14015 aip->shockwave_object = -1;
14016 return 0;
14017 }
14018
14019 float danger_dist;
14020 vec3d expected_pos; // Position at which we expect the weapon to detonate.
14021 int pos_set = 0;
14022
14023 danger_dist = wip->shockwave.outer_rad;
14024 // Set predicted position of detonation.
14025 // If an aspect locked missile, assume it will detonate at the homing position.
14026 // If not, which is not possible in a default FreeSpace weapon, then predict it will detonate at some
14027 // time in the future, this time based on max lifetime and life left.
14028 if (wip->is_locked_homing()) {
14029 expected_pos = weaponp->homing_pos;
14030 if (weaponp->homing_object && weaponp->homing_object->type == OBJ_SHIP) {
14031 target_ship_obj = weaponp->homing_object;
14032 }
14033 pos_set = 1;
14034 if (IS_VEC_NULL(&weaponp->homing_pos)) {
14035 pos_set = 0;
14036 if (weaponp->target_num != -1) {
14037 if (Objects[weaponp->target_num].type == OBJ_SHIP) {
14038 target_ship_obj = &Objects[weaponp->target_num];
14039 expected_pos = target_ship_obj->pos;
14040 pos_set = 1;
14041 }
14042 }
14043 }
14044 }
14045
14046 if (!pos_set) {
14047 float time_scale;
14048
14049 if (wip->lifetime - weaponp->lifeleft > 5.0f) {
14050 time_scale = 1.0f;
14051 } else {
14052 time_scale = weaponp->lifeleft/2.0f;
14053 }
14054
14055 vm_vec_scale_add(&expected_pos, &weapon_objp->pos, &weapon_objp->orient.vec.fvec, time_scale);
14056 }
14057
14058 // See if too far away to care about shockwave.
14059 if (vm_vec_dist_quick(&objp->pos, &expected_pos) > danger_dist*2.0f) {
14060 //aip->ai_flags &= ~AIF_AVOID_SHOCKWAVE_WEAPON;
14061 return 0;
14062 } else {
14063 // try to find a safe position
14064 vec3d vec_from_exp;
14065 float dir = 1.0f;
14066 vm_vec_sub(&vec_from_exp, &objp->pos, &expected_pos);
14067 float dot = vm_vec_dot(&vec_from_exp, &weapon_objp->orient.vec.fvec);
14068 if (dot > -30) {
14069 // if we're already on the other side of the explosion, don't try to fly behind it
14070 dir = -1.0f;
14071 }
14072
14073 // Fly towards a point behind the weapon.
14074 vm_vec_scale_add(safe_pos, &weapon_objp->pos, &weapon_objp->orient.vec.fvec, -50000.0f*dir);
14075
14076 return 1;
14077 }
14078 } else if (aip->ai_flags[AI::AI_Flags::Avoid_shockwave_ship]) {
14079 if (aip->shockwave_object == -1) {
14080 int shockwave_ship = ai_find_shockwave_ship(objp);
14081 if (shockwave_ship == -1) {
14082 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
14083 return 0;
14084 } else {
14085 aip->shockwave_object = shockwave_ship;
14086 }
14087 }
14088
14089 Assert(aip->shockwave_object > -1);
14090 object *ship_objp = &Objects[aip->shockwave_object];
14091 if (ship_objp == objp) {
14092 aip->shockwave_object = -1;
14093 return 0;
14094 }
14095
14096 if (ship_objp->type != OBJ_SHIP) {
14097 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
14098 return 0;
14099 }
14100
14101 // Optimize note! Don't really have to normalize. We only need a point away from the blowing-up ship.
14102 vec3d safe_vec;
14103
14104 vm_vec_normalized_dir(&safe_vec, &objp->pos, &ship_objp->pos);
14105 vm_vec_scale_add(safe_pos, &ship_objp->pos, &safe_vec, 50000.0f); // Fly away from the ship.
14106
14107 float outer_rad = ship_get_exp_outer_rad(ship_objp);
14108
14109 if (vm_vec_dist_quick(&objp->pos, &ship_objp->pos) > outer_rad*1.5f) {
14110 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
14111 return 0;
14112 }
14113
14114 return 1;
14115
14116 } else {
14117 Int3(); // Illegal -- supposedly avoiding a shockwave, but neither ship nor weapon. What is it!?
14118 }
14119
14120 return 0;
14121 }
14122
14123 // --------------------------------------------------------------------------
14124 // Make object *objp avoid the nearest dangerous shockwave-producing weapon.
14125 // If it looks like there is no valid shockwave-producing weapon then clear the AIF_AVOID_SHOCKWAVE_WEAPON bit in ai_flags and return.
14126 // Return 1 if avoiding a shockwave, else return 0.
ai_avoid_shockwave(object * objp,ai_info * aip)14127 int ai_avoid_shockwave(object *objp, ai_info *aip)
14128 {
14129 vec3d safe_pos;
14130
14131 // BIG|HUGE do not respond to shockwaves
14132 // Goober5000 - let's treat shockwave response the same way whether from weapon or ship
14133 if (!Ship_info[Ships[objp->instance].ship_info_index].avoids_shockwaves()) {
14134 // don't come here again
14135 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_ship);
14136 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_weapon);
14137 return 0;
14138 }
14139
14140 // Don't all react right away.
14141 if (!(aip->ai_flags[AI::AI_Flags::Avoid_shockwave_started])) {
14142 float evadeChance = (aip->ai_shockwave_evade_chance == FLT_MIN)
14143 ? ((float) aip->ai_class/4.0f + 0.25f)
14144 : aip->ai_shockwave_evade_chance;
14145 if (!rand_chance(flFrametime, evadeChance)) // Chance to avoid in 1 second is 0.25 + ai_class/4
14146 return 0;
14147 }
14148
14149 if (!aas_1(objp, aip, &safe_pos)) {
14150 aip->ai_flags.set(AI::AI_Flags::Avoid_shockwave_started);
14151 return 0;
14152 }
14153
14154 aip->ai_flags.set(AI::AI_Flags::Avoid_shockwave_started);
14155
14156 // OK, evade the shockwave!
14157 turn_towards_point(objp, &safe_pos, NULL, 0.0f);
14158 vec3d vec_to_safe_pos;
14159 float dot_to_goal;
14160
14161 vm_vec_normalized_dir(&vec_to_safe_pos, &safe_pos, &objp->pos);
14162
14163 dot_to_goal = vm_vec_dot(&objp->orient.vec.fvec, &vec_to_safe_pos);
14164 if (dot_to_goal < -0.5f)
14165 accelerate_ship(aip, 0.3f);
14166 else {
14167 accelerate_ship(aip, 1.0f + dot_to_goal);
14168 if (dot_to_goal > 0.2f) {
14169 if (!(objp->phys_info.flags & PF_AFTERBURNER_ON )) {
14170 afterburners_start(objp);
14171 aip->afterburner_stop_time = Missiontime + 2*F1_0;
14172 }
14173 }
14174 }
14175
14176 return 1;
14177 }
14178
14179 // Awaiting repair. Be useful.
14180 // Probably fly towards incoming repair ship.
14181 // Return true if this ship is close to being repaired, else return false.
ai_await_repair_frame(object * objp,ai_info * aip)14182 int ai_await_repair_frame(object *objp, ai_info *aip)
14183 {
14184 if (!(aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair]))
14185 return 0;
14186
14187 if (aip->support_ship_objnum == -1)
14188 return 0;
14189
14190 ship *shipp;
14191 ship_info *sip;
14192
14193 shipp = &Ships[Objects[aip->support_ship_objnum].instance];
14194 sip = &Ship_info[shipp->ship_info_index];
14195
14196 aip->ai_flags.remove(AI::AI_Flags::Formation_object); // Prevents endless rotation.
14197
14198 if (!(sip->flags[Ship::Info_Flags::Support]))
14199 return 0;
14200
14201 vec3d goal_point;
14202 object *repair_objp;
14203
14204 repair_objp = &Objects[aip->support_ship_objnum];
14205
14206 if (Ships[repair_objp->instance].team == Iff_traitor) {
14207 ai_abort_rearm_request(repair_objp);
14208 return 0;
14209 }
14210
14211 vm_vec_scale_add(&goal_point, &repair_objp->pos, &repair_objp->orient.vec.uvec, -50.0f); // Fly towards point below repair ship.
14212
14213 vec3d vtr;
14214 float dist = vm_vec_normalized_dir(&vtr, &goal_point, &objp->pos);
14215 float dot = vm_vec_dot(&vtr, &objp->orient.vec.fvec);
14216
14217 if (dist > 200.0f) {
14218 accelerate_ship(aip, (0.9f + dot) * dist/1500.0f);
14219 turn_towards_point(objp, &goal_point, NULL, 0.0f);
14220 } else {
14221 accelerate_ship(aip, 0.0f);
14222 }
14223
14224 return 1;
14225 }
14226
14227 // Maybe cause this ship to self-destruct.
14228 // Currently, any small ship (SIF_SMALL_SHIP) that has been disabled will self-destruct after awhile.
14229 // Maybe should only do this if they are preventing their wing from re-entering.
ai_maybe_self_destruct(object * objp,ai_info * aip)14230 void ai_maybe_self_destruct(object *objp, ai_info *aip)
14231 {
14232 Assertion(objp->type == OBJ_SHIP, "ai_maybe_self_destruct() can only be called with objects that are ships!");
14233 ship *shipp = &Ships[objp->instance];
14234
14235 // Some IFFs can be repaired, so no self-destruct.
14236 // In multiplayer, just don't self-destruct. I figured there would be a problem. -- MK, 3/19/98.
14237 if ((Iff_info[shipp->team].flags & IFFF_SUPPORT_ALLOWED) || (Game_mode & GM_MULTIPLAYER))
14238 return;
14239
14240 // Small ships in a wing blow themselves up after awhile if engine or weapons system has been destroyed.
14241 // Reason: Don't want them to prevent a re-emergence of the wing.
14242 // Note: Don't blow up if not in a wing for two reasons: One, won't affect re-emergence of waves and (1) disable the Dragon
14243 // mission would be broken.
14244 // Also, don't blow up the ship if it has a ship flag preventing this - Goober5000
14245 if ((Ship_info[shipp->ship_info_index].is_small_ship()) && (shipp->wingnum >= 0) && !(shipp->flags[Ship::Ship_Flags::No_disabled_self_destruct])) {
14246 if ((ship_get_subsystem_strength(shipp, SUBSYSTEM_ENGINE) <= 0.0f) ||
14247 (ship_get_subsystem_strength(shipp, SUBSYSTEM_WEAPONS) <= 0.0f)) {
14248 if (aip->self_destruct_timestamp < 0)
14249 aip->self_destruct_timestamp = timestamp(90 * 1000); // seconds until self-destruct
14250 } else {
14251 aip->self_destruct_timestamp = -1;
14252 }
14253
14254 if (aip->self_destruct_timestamp < 0) {
14255 return;
14256 }
14257
14258 if (timestamp_elapsed(aip->self_destruct_timestamp)) {
14259 ship_apply_local_damage( objp, objp, &objp->pos, objp->hull_strength*flFrametime + 1.0f, -1, MISS_SHIELDS);
14260 }
14261 }
14262 }
14263
14264 /**
14265 * Determine if pl_objp needs a new target, called from ai_frame()
14266 */
ai_need_new_target(object * pl_objp,int target_objnum)14267 int ai_need_new_target(object *pl_objp, int target_objnum)
14268 {
14269 object *objp;
14270
14271 if ( target_objnum < 0 ) {
14272 return 1;
14273 }
14274
14275 objp = &Objects[target_objnum];
14276
14277 if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) && (objp->type != OBJ_WEAPON) ) {
14278 return 1;
14279 }
14280
14281 if ( objp->type == OBJ_SHIP ) {
14282 if ( Ships[objp->instance].flags[Ship::Ship_Flags::Dying] ) {
14283 return 1;
14284 } else if (Ships[objp->instance].team == Ships[pl_objp->instance].team) {
14285 // Goober5000 - targeting the same team is allowed if pl_objp is going bonkers
14286 ai_info *pl_aip = &Ai_info[Ships[pl_objp->instance].ai_index];
14287 if (pl_aip->active_goal != AI_GOAL_NONE && pl_aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) {
14288 if (pl_aip->goals[pl_aip->active_goal].flags[AI::Goal_Flags::Target_own_team]) {
14289 return 0;
14290 }
14291 }
14292
14293 return 1;
14294 }
14295 }
14296
14297 return 0;
14298 }
14299
14300 /**
14301 * If *objp is recovering from a collision with a big ship, handle it.
14302 * @return true if recovering.
14303 */
maybe_big_ship_collide_recover_frame(object * objp,ai_info * aip)14304 int maybe_big_ship_collide_recover_frame(object *objp, ai_info *aip)
14305 {
14306 float dot, dist;
14307 vec3d v2g;
14308
14309 bool better_collision_avoid_and_fighting = The_mission.ai_profile->flags[AI::Profile_Flags::Better_collision_avoidance] && aip->mode == AIM_CHASE;
14310
14311 // if this guy's in battle he doesn't have the time to spend up to 35 seconds recovering!
14312 int phase1_time = better_collision_avoid_and_fighting ? 2 : 5;
14313 int phase2_time = 30;
14314
14315 if (aip->ai_flags[AI::AI_Flags::Big_ship_collide_recover_1]) {
14316 vec3d global_recover_pos = aip->big_recover_1_direction + objp->pos;
14317 ai_turn_towards_vector(&global_recover_pos, objp, nullptr, nullptr, 0.0f, 0, nullptr);
14318 dist = vm_vec_normalized_dir(&v2g, &global_recover_pos, &objp->pos);
14319 dot = vm_vec_dot(&objp->orient.vec.fvec, &v2g);
14320 accelerate_ship(aip, dot);
14321
14322 // If close to desired point, or long enough since entered this mode, continue to next mode.
14323 if ((timestamp_until(aip->big_recover_timestamp) < -phase1_time * 1000) || (dist < (0.5f + flFrametime) * objp->phys_info.speed)) {
14324 aip->ai_flags.remove(AI::AI_Flags::Big_ship_collide_recover_1);
14325 if (better_collision_avoid_and_fighting) // if true, bug out here 2 seconds should be enough
14326 aip->ai_flags.remove(AI::AI_Flags::Target_collision);
14327 else
14328 aip->ai_flags.set(AI::AI_Flags::Big_ship_collide_recover_2);
14329 }
14330
14331 return 1;
14332
14333 } else if (aip->ai_flags[AI::AI_Flags::Big_ship_collide_recover_2]) {
14334 ai_turn_towards_vector(&aip->big_recover_2_pos, objp, nullptr, nullptr, 0.0f, 0, nullptr);
14335 dist = vm_vec_normalized_dir(&v2g, &aip->big_recover_2_pos, &objp->pos);
14336 dot = vm_vec_dot(&objp->orient.vec.fvec, &v2g);
14337 accelerate_ship(aip, dot);
14338
14339 // If close to desired point, or 30+ seconds since started avoiding collision, done avoiding.
14340 // TODO: below comment (this never gets tripped because this is like one of the first things AI do, so it won't be following any dynamic goals yet)
14341 // also done avoiding if you're using better collision avoid and youve starting fighting
14342 if ((timestamp_until(aip->big_recover_timestamp) < -phase2_time * 1000) || (dist < (0.5f + flFrametime) * objp->phys_info.speed) || better_collision_avoid_and_fighting) {
14343 aip->ai_flags.remove(AI::AI_Flags::Big_ship_collide_recover_2);
14344 aip->ai_flags.remove(AI::AI_Flags::Target_collision);
14345 }
14346
14347 return 1;
14348 }
14349
14350 if (aip->ai_flags[AI::AI_Flags::Target_collision]) {
14351 aip->ai_flags.remove(AI::AI_Flags::Target_collision);
14352 }
14353 return 0;
14354 }
14355
validate_mode_submode(ai_info * aip)14356 void validate_mode_submode(ai_info *aip)
14357 {
14358 switch (aip->mode) {
14359 case AIM_CHASE:
14360 // check valid submode
14361 switch (aip->submode) {
14362 case SM_CONTINUOUS_TURN:
14363 case SM_ATTACK:
14364 case SM_EVADE_SQUIGGLE:
14365 case SM_EVADE_BRAKE:
14366 case SM_EVADE:
14367 case SM_SUPER_ATTACK:
14368 case SM_AVOID:
14369 case SM_GET_BEHIND:
14370 case SM_GET_AWAY:
14371 case SM_EVADE_WEAPON:
14372 case SM_FLY_AWAY:
14373 case SM_ATTACK_FOREVER:
14374 break;
14375 default:
14376 Int3();
14377 }
14378 break;
14379
14380 case AIM_STRAFE:
14381 // check valid submode
14382 switch(aip->submode) {
14383 case AIS_STRAFE_ATTACK:
14384 case AIS_STRAFE_AVOID:
14385 case AIS_STRAFE_RETREAT1:
14386 case AIS_STRAFE_RETREAT2:
14387 case AIS_STRAFE_POSITION:
14388 break;
14389 default:
14390 Int3();
14391 }
14392 break;
14393 }
14394 }
14395
14396 /**
14397 * Process AI object "objnum".
14398 */
ai_frame(int objnum)14399 void ai_frame(int objnum)
14400 {
14401 ship *shipp = &Ships[Objects[objnum].instance];
14402 ai_info *aip = &Ai_info[shipp->ai_index];
14403 int target_objnum;
14404
14405 Assert((aip->mode != AIM_WAYPOINTS) || (aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC));
14406
14407 // Set globals defining the current object and its enemy object.
14408 Pl_objp = &Objects[objnum];
14409
14410 //Default to glide OFF
14411 Pl_objp->phys_info.flags &= ~PF_GLIDING;
14412
14413 // warping out?
14414 if ((aip->mode == AIM_WARP_OUT) || (aip->ai_flags[AI::AI_Flags::Trying_unsuccessfully_to_warp]))
14415 {
14416 ai_warp_out(Pl_objp);
14417
14418 // Goober5000 - either we were never trying unsuccessfully, or we were but now
14419 // we're successful... in either case, since we're actually warping we simply return
14420 if (!(aip->ai_flags[AI::AI_Flags::Trying_unsuccessfully_to_warp]))
14421 return;
14422 }
14423
14424 ai_maybe_self_destruct(Pl_objp, aip);
14425 ai_process_mission_orders( objnum, aip );
14426
14427 // Avoid a shockwave, if necessary. If a shockwave and rearming, stop rearming.
14428 if (aip->mode != AIM_PLAY_DEAD && aip->ai_flags[AI::AI_Flags::Avoid_shockwave_ship, AI::AI_Flags::Avoid_shockwave_weapon]) {
14429 if (ai_avoid_shockwave(Pl_objp, aip)) {
14430 aip->ai_flags.remove(AI::AI_Flags::Big_ship_collide_recover_1);
14431 aip->ai_flags.remove(AI::AI_Flags::Big_ship_collide_recover_2);
14432 if (aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair])
14433 ai_abort_rearm_request(Pl_objp);
14434 return;
14435 }
14436 } else {
14437 aip->ai_flags.remove(AI::AI_Flags::Avoid_shockwave_started);
14438 }
14439
14440 // moved call to ai_do_repair frame here from below because of the subsequent if statment returning
14441 // if the ship is getting repaired
14442 // If waiting to be repaired, just stop and sit.
14443 ai_do_repair_frame(Pl_objp, aip, flFrametime);
14444 if ((aip->ai_flags[AI::AI_Flags::Awaiting_repair]) || (aip->ai_flags[AI::AI_Flags::Being_repaired])) {
14445 if (ai_await_repair_frame(Pl_objp, aip))
14446 return;
14447 }
14448
14449 if (aip->mode == AIM_PLAY_DEAD)
14450 return;
14451
14452 // If recovering from a collision with a big ship, don't continue.
14453 if (maybe_big_ship_collide_recover_frame(Pl_objp, aip))
14454 return;
14455
14456 ai_preprocess_ignore_objnum(aip);
14457 target_objnum = set_target_objnum(aip, aip->target_objnum);
14458
14459 Assert(objnum != target_objnum);
14460
14461 ai_manage_shield(Pl_objp, aip);
14462
14463 if ( maybe_request_support(Pl_objp) ) {
14464 if ( Ships[Pl_objp->instance].flags[Ship::Ship_Flags::From_player_wing] ) {
14465 ship_maybe_tell_about_rearm(shipp);
14466 }
14467 }
14468 else {
14469 ship_maybe_tell_about_low_ammo(shipp);
14470 }
14471
14472 ai_maybe_depart(Pl_objp);
14473
14474 // Find an enemy if don't already have one.
14475 En_objp = NULL;
14476 if ( ai_need_new_target(Pl_objp, target_objnum) ) {
14477 if ((aip->mode != AIM_EVADE_WEAPON) && (aip->active_goal == AI_ACTIVE_GOAL_DYNAMIC)) {
14478 aip->resume_goal_time = -1;
14479 aip->active_goal = AI_GOAL_NONE;
14480 } else if (aip->resume_goal_time == -1) {
14481 // AL 12-9-97: Don't allow cargo and navbuoys to set their aip->target_objnum
14482 if ( Ship_info[shipp->ship_info_index].class_type > -1 && (Ship_types[Ship_info[shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_auto_attacks]) ) {
14483 target_objnum = find_enemy(objnum, MAX_ENEMY_DISTANCE, The_mission.ai_profile->max_attackers[Game_skill_level]); // Attack up to 25K units away.
14484 if (target_objnum != -1) {
14485 if (aip->target_objnum != target_objnum)
14486 aip->aspect_locked_time = 0.0f;
14487 target_objnum = set_target_objnum(aip, target_objnum);
14488
14489 if (target_objnum >= 0)
14490 {
14491 En_objp = &Objects[target_objnum];
14492 }
14493 }
14494 }
14495 }
14496 } else if (target_objnum >= 0) {
14497 En_objp = &Objects[target_objnum];
14498 }
14499
14500 // set base stealth info each frame
14501 aip->ai_flags.remove(AI::AI_Flags::Stealth_pursuit);
14502 if (En_objp && En_objp->type == OBJ_SHIP) {
14503 if (Ships[En_objp->instance].flags[Ship::Ship_Flags::Stealth]) {
14504 int stealth_state = ai_is_stealth_visible(Pl_objp, En_objp);
14505 float dist = vm_vec_dist_quick(&En_objp->pos, &Pl_objp->pos);
14506
14507 if (stealth_state != STEALTH_FULLY_TARGETABLE) {
14508 aip->ai_flags.set(AI::AI_Flags::Stealth_pursuit);
14509 }
14510
14511 if ( (stealth_state == STEALTH_FULLY_TARGETABLE) || (stealth_state == STEALTH_IN_FRUSTUM) ) {
14512 aip->stealth_last_visible_stamp = timestamp();
14513 aip->stealth_last_cheat_visible_stamp = aip->stealth_last_visible_stamp;
14514 aip->stealth_last_pos = En_objp->pos;
14515 aip->stealth_velocity = En_objp->phys_info.vel;
14516 } else if (dist < 100) {
14517 // get cheat timestamp
14518 aip->stealth_last_cheat_visible_stamp = timestamp();
14519
14520 // set approximate pos and vel, with increasing error as time from last_visible_stamp increases
14521 update_ai_stealth_info_with_error(aip/*, 0*/);
14522 }
14523 }
14524 }
14525
14526 // AL 12-10-97: ensure that cargo and navbuoys aip->target_objnum is always -1.
14527 if ( Ship_info[shipp->ship_info_index].class_type > -1 && !(Ship_types[Ship_info[shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_auto_attacks])) {
14528 aip->target_objnum = -1;
14529 }
14530
14531 if ((En_objp != NULL) && (En_objp->pos.xyz.x == Pl_objp->pos.xyz.x) && (En_objp->pos.xyz.y == Pl_objp->pos.xyz.y) && (En_objp->pos.xyz.z == Pl_objp->pos.xyz.z)) {
14532 mprintf(("Warning: Object and its enemy have same position. Object #%d\n", OBJ_INDEX(Pl_objp)));
14533 En_objp = NULL;
14534 }
14535
14536 if (aip->mode == AIM_CHASE) {
14537 if (En_objp == NULL) {
14538 aip->active_goal = -1;
14539 }
14540 }
14541
14542 // If there is a goal to resume and enough time has elapsed, resume the goal.
14543 if ((aip->resume_goal_time > 0) && (aip->resume_goal_time < Missiontime)) {
14544 aip->active_goal = AI_GOAL_NONE;
14545 aip->resume_goal_time = -1;
14546 target_objnum = find_enemy(objnum, 2000.0f, The_mission.ai_profile->max_attackers[Game_skill_level]);
14547 if (target_objnum != -1) {
14548 if (aip->target_objnum != target_objnum) {
14549 aip->aspect_locked_time = 0.0f;
14550 }
14551 set_target_objnum(aip, target_objnum);
14552 }
14553 }
14554
14555 // check if targeted subsystem has been destroyed, if so, move onto another subsystem
14556 // if trying to disable or disarm the target
14557 if ((En_objp != NULL) && ( aip->targeted_subsys != NULL )) {
14558 Assert(En_objp->type == OBJ_SHIP);
14559 if ( aip->targeted_subsys->current_hits <= 0.0f ) {
14560 int subsys_type;
14561
14562 if ( aip->goals[0].ai_mode == AI_GOAL_DISABLE_SHIP ) {
14563 subsys_type = SUBSYSTEM_ENGINE;
14564 } else if ( aip->goals[0].ai_mode == AI_GOAL_DISARM_SHIP ) {
14565 subsys_type = SUBSYSTEM_TURRET;
14566 } else {
14567 subsys_type = -1;
14568 }
14569
14570 if ( subsys_type != -1 ) {
14571 ship_subsys *new_subsys;
14572 new_subsys = ship_return_next_subsys(&Ships[En_objp->instance], subsys_type, &Pl_objp->pos);
14573 if ( new_subsys != NULL ) {
14574 set_targeted_subsys(aip, new_subsys, aip->target_objnum);
14575 } else {
14576 // AL 12-16-97: no more subsystems to attack... reset targeting info
14577 aip->target_objnum = -1;
14578 set_targeted_subsys(aip, NULL, -1);
14579 }
14580 } else {
14581 // targeted subsys is destroyed, so stop attacking it
14582 set_targeted_subsys(aip, NULL, -1);
14583 }
14584 }
14585 }
14586
14587 ai_maybe_launch_cmeasure(Pl_objp, aip);
14588 ai_maybe_evade_locked_missile(Pl_objp, aip);
14589
14590 aip->target_time += flFrametime;
14591
14592 int in_formation = 0;
14593 if (aip->ai_flags[AI::AI_Flags::Formation_object, AI::AI_Flags::Formation_wing]) {
14594 in_formation = !ai_formation();
14595 }
14596
14597 if ( !in_formation ) {
14598 ai_execute_behavior(aip);
14599 }
14600
14601 process_subobjects(objnum);
14602 maybe_resume_previous_mode(Pl_objp, aip);
14603
14604 if (Pl_objp->phys_info.flags & PF_AFTERBURNER_ON ) {
14605 if (Missiontime > aip->afterburner_stop_time) {
14606 afterburners_stop(Pl_objp);
14607 }
14608 }
14609 }
14610
ai_control_info_check(ai_info * aip,ship_info * sip,physics_info * pi)14611 static void ai_control_info_check(ai_info *aip, ship_info *sip, physics_info *pi)
14612 {
14613 if (aip->ai_override_flags.none_set())
14614 return;
14615
14616 bool no_lat = false;
14617 bool no_rot = false;
14618
14619 if (!aip->ai_override_flags[AI::Maneuver_Override_Flags::Lateral_never_expire] && timestamp_elapsed(aip->ai_override_lat_timestamp))
14620 {
14621 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Sideways);
14622 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Up);
14623 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Forward);
14624 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Full_lat);
14625 no_lat = true;
14626 }
14627
14628 if (!aip->ai_override_flags[AI::Maneuver_Override_Flags::Rotational_never_expire] && timestamp_elapsed(aip->ai_override_rot_timestamp))
14629 {
14630 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Heading);
14631 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Roll);
14632 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Pitch);
14633 aip->ai_override_flags.remove(AI::Maneuver_Override_Flags::Full_rot);
14634 no_rot = true;
14635 }
14636
14637 if (no_rot && no_lat) {
14638 aip->ai_override_flags.reset();
14639 }
14640 else
14641 {
14642 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Full_rot])
14643 {
14644 AI_ci.pitch = aip->ai_override_ci.pitch;
14645 AI_ci.heading = aip->ai_override_ci.heading;
14646 AI_ci.bank = aip->ai_override_ci.bank;
14647 }
14648 else
14649 {
14650 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Pitch])
14651 {
14652 AI_ci.pitch = aip->ai_override_ci.pitch;
14653 }
14654 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Heading])
14655 {
14656 AI_ci.heading = aip->ai_override_ci.heading;
14657 }
14658 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Roll])
14659 {
14660 AI_ci.bank = aip->ai_override_ci.bank;
14661 }
14662 }
14663
14664 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Full_lat])
14665 {
14666 AI_ci.vertical = aip->ai_override_ci.vertical;
14667 AI_ci.sideways = aip->ai_override_ci.sideways;
14668 AI_ci.forward = aip->ai_override_ci.forward;
14669 }
14670 else
14671 {
14672 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Up])
14673 {
14674 AI_ci.vertical = aip->ai_override_ci.vertical;
14675 }
14676 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Sideways])
14677 {
14678 AI_ci.sideways = aip->ai_override_ci.sideways;
14679 }
14680 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Forward])
14681 {
14682 AI_ci.forward = aip->ai_override_ci.forward;
14683 }
14684 }
14685
14686 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Dont_bank_when_turning] || sip->flags[Ship::Info_Flags::Dont_bank_when_turning])
14687 AI_ci.control_flags |= CIF_DONT_BANK_WHEN_TURNING;
14688 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Dont_clamp_max_velocity] || sip->flags[Ship::Info_Flags::Dont_clamp_max_velocity])
14689 AI_ci.control_flags |= CIF_DONT_CLAMP_MAX_VELOCITY;
14690 if (aip->ai_override_flags[AI::Maneuver_Override_Flags::Instantaneous_acceleration] || sip->flags[Ship::Info_Flags::Instantaneous_acceleration])
14691 AI_ci.control_flags |= CIF_INSTANTANEOUS_ACCELERATION;
14692 }
14693
14694 // set physics flag according to whether we are instantaneously accelerating
14695 if (AI_ci.control_flags & CIF_INSTANTANEOUS_ACCELERATION)
14696 pi->flags |= PF_NO_DAMP;
14697 else
14698 pi->flags &= ~PF_NO_DAMP;
14699 }
14700
14701 int Last_ai_obj = -1;
14702
ai_process(object * obj,int ai_index,float frametime)14703 void ai_process( object * obj, int ai_index, float frametime )
14704 {
14705 if (obj->flags[Object::Object_Flags::Should_be_dead])
14706 return;
14707
14708 Assert(obj->type == OBJ_SHIP);
14709 auto shipp = &Ships[obj->instance];
14710 auto sip = &Ship_info[shipp->ship_info_index];
14711
14712 // return if ship is dead, unless it's a big ship...then its turrets still fire, like I was quoted in a magazine. -- MK, 5/15/98.
14713 if ((shipp->flags[Ship::Ship_Flags::Dying] ) && !(sip->is_big_or_huge())) {
14714 return;
14715 }
14716
14717 bool rfc = true; // Assume will be Reading Flying Controls.
14718
14719 Assert( ai_index >= 0 );
14720 auto aip = &Ai_info[shipp->ai_index];
14721
14722 AI_frametime = frametime;
14723 if (OBJ_INDEX(obj) <= Last_ai_obj) {
14724 AI_FrameCount++;
14725 }
14726
14727 memset( &AI_ci, 0, sizeof(AI_ci) );
14728
14729 ai_frame(OBJ_INDEX(obj));
14730
14731 AI_ci.pitch = 0.0f;
14732 AI_ci.bank = 0.0f;
14733 AI_ci.heading = 0.0f;
14734
14735 // the ships maximum velocity now depends on the energy flowing to engines
14736 obj->phys_info.max_vel.xyz.z = shipp->current_max_speed;
14737
14738 // In certain circumstances, the AI says don't fly in the normal way.
14739 // One circumstance is in docking and undocking, when the ship is moving
14740 // under thruster control.
14741 switch (aip->mode) {
14742 case AIM_DOCK:
14743 if ((aip->submode >= AIS_DOCK_2) && (aip->submode != AIS_UNDOCK_3))
14744 rfc = false;
14745 break;
14746 case AIM_WARP_OUT:
14747 if (aip->submode >= AIS_WARP_3)
14748 rfc = false;
14749 break;
14750 default:
14751 break;
14752 }
14753
14754 // this is sufficient because non-big ships were already weeded out by dying above
14755 if (shipp->flags[Ship::Ship_Flags::Dying] && sip->flags[Ship::Info_Flags::Large_ship_deathroll])
14756 rfc = false;
14757
14758 if (rfc) {
14759 // Wanderer - sexp based override goes here - only if rfc is valid though
14760 ai_control_info_check(aip, sip, &obj->phys_info);
14761 physics_read_flying_controls( &obj->orient, &obj->phys_info, &AI_ci, frametime);
14762 }
14763
14764 Last_ai_obj = OBJ_INDEX(obj);
14765 }
14766
14767 /**
14768 * Initialize ::ai_info struct of object objnum.
14769 */
init_ai_object(int objnum)14770 void init_ai_object(int objnum)
14771 {
14772 int i, ship_index, ai_index, ship_type;
14773 ai_info *aip;
14774 object *objp;
14775 vec3d near_vec; // A vector nearby and mainly in front of this object.
14776
14777 objp = &Objects[objnum];
14778 ship_index = objp->instance;
14779 ai_index = Ships[ship_index].ai_index;
14780 Assert((ai_index >= 0) && (ai_index < MAX_AI_INFO));
14781
14782 aip = &Ai_info[ai_index];
14783
14784 ship_type = Ships[ship_index].ship_info_index;
14785
14786 vm_vec_scale_add(&near_vec, &objp->pos, &objp->orient.vec.fvec, 100.0f);
14787 vm_vec_scale_add2(&near_vec, &objp->orient.vec.rvec, 10.0f);
14788
14789 // Things that shouldn't have to get initialized, but initialize them just in case!
14790 aip->ai_flags.reset();
14791 aip->previous_mode = AIM_NONE;
14792 aip->mode_time = -1;
14793 aip->target_objnum = -1;
14794 aip->target_signature = -1;
14795 aip->previous_target_objnum = -1;
14796 aip->target_time = 0.0f;
14797 aip->enemy_wing = -1;
14798 aip->attacker_objnum = -1;
14799 aip->goal_objnum = -1;
14800 aip->goal_signature = -1;
14801 aip->guard_objnum = -1;
14802 aip->guard_signature = -1;
14803 aip->guard_wingnum = -1;
14804 aip->submode = 0;
14805 aip->previous_submode = 0;
14806 aip->best_dot_to_enemy = -1.0f;
14807 aip->best_dot_from_enemy = -1.0f;
14808 aip->best_dot_to_time = 0;
14809 aip->best_dot_from_time = 0;
14810 aip->submode_start_time = 0;
14811 aip->submode_parm0 = 0;
14812 aip->submode_parm1 = 0;
14813 aip->active_goal = -1;
14814 aip->goal_check_time = timestamp(0);
14815 aip->last_predicted_enemy_pos = near_vec;
14816 aip->prev_goal_point = near_vec;
14817 aip->goal_point = near_vec;
14818 aip->time_enemy_in_range = 0.0f;
14819 aip->time_enemy_near = 0.0f;
14820 aip->last_attack_time = 0;
14821 aip->last_hit_time = 0;
14822 aip->last_hit_quadrant = 0;
14823 aip->hitter_objnum = -1;
14824 aip->hitter_signature = -1;
14825 aip->resume_goal_time = -1;
14826 aip->prev_accel = 0.0f;
14827 aip->prev_dot_to_goal = 0.0f;
14828
14829 aip->ignore_objnum = UNUSED_OBJNUM;
14830 aip->ignore_signature = -1;
14831
14832 // Goober5000
14833 for (i = 0; i < MAX_IGNORE_NEW_OBJECTS; i++)
14834 {
14835 aip->ignore_new_objnums[i] = UNUSED_OBJNUM;
14836 aip->ignore_new_signatures[i] = -1;
14837 }
14838
14839 //Init stuff from AI class and AI profiles
14840 init_aip_from_class_and_profile(aip, &Ai_classes[Ship_info[ship_type].ai_class], The_mission.ai_profile);
14841
14842 aip->wp_list = NULL;
14843 aip->wp_index = INVALID_WAYPOINT_POSITION;
14844
14845 aip->attacker_objnum = -1;
14846 aip->goal_signature = -1;
14847
14848 Objects[objnum].phys_info.prev_fvec = Objects[objnum].orient.vec.fvec;
14849
14850 aip->last_predicted_enemy_pos.xyz.x = 0.0f; // Says this value needs to be recomputed!
14851 aip->time_enemy_in_range = 0.0f;
14852 aip->time_enemy_near = 0.0f;
14853
14854 aip->resume_goal_time = -1; // Say there is no goal to resume.
14855
14856 aip->active_goal = -1;
14857 aip->path_start = -1;
14858 aip->path_goal_dist = -1;
14859 aip->path_length = 0;
14860 aip->path_subsystem_next_check = 1;
14861
14862 aip->support_ship_objnum = -1;
14863 aip->support_ship_signature = -1;
14864
14865 aip->danger_weapon_objnum = -1;
14866 aip->danger_weapon_signature = -1;
14867
14868 aip->last_hit_target_time = Missiontime;
14869 aip->last_hit_time = Missiontime;
14870
14871 aip->nearest_locked_object = -1;
14872 aip->nearest_locked_distance = 99999.0f;
14873
14874 aip->targeted_subsys = NULL;
14875 aip->last_subsys_target = NULL;
14876 aip->targeted_subsys_parent = -1;
14877
14878 // The next two fields are used to time the rearming to allow useful sound effects for missile rearming
14879 aip->rearm_first_missile = TRUE; // flag to indicate that next missile to load is the first missile
14880 aip->rearm_first_ballistic_primary = TRUE; // flag to indicate that next ballistic to load is the first ballistic
14881 aip->rearm_release_delay = 0; // timestamp to delay the separation of docked ships after rearm
14882
14883 aip->next_predict_pos_time = 0;
14884 aip->next_aim_pos_time = 0;
14885
14886 aip->afterburner_stop_time = 0;
14887 aip->last_objsig_hit = -1; // object signature of the ship most recently hit by aip
14888
14889 aip->path_next_create_time = timestamp(1);
14890 aip->path_create_pos = Objects[objnum].pos;
14891 aip->path_create_orient = Objects[objnum].orient;
14892 vm_vec_zero(&aip->path_depart_orient);
14893
14894 aip->ignore_expire_timestamp = timestamp(1);
14895 aip->warp_out_timestamp = 0;
14896 aip->next_rearm_request_timestamp = timestamp(1);
14897 aip->primary_select_timestamp = timestamp(1);
14898 aip->secondary_select_timestamp = timestamp(1);
14899 aip->scan_for_enemy_timestamp = timestamp(1);
14900
14901 aip->choose_enemy_timestamp = timestamp(3*(NUM_SKILL_LEVELS-Game_skill_level) * ((static_rand_alt() % 500) + 500));
14902
14903 aip->shockwave_object = -1;
14904 aip->shield_manage_timestamp = timestamp(1);
14905 aip->self_destruct_timestamp = -1; // This is a flag that we have not yet set this.
14906 aip->ok_to_target_timestamp = timestamp(1);
14907 aip->pick_big_attack_point_timestamp = timestamp(1);
14908 vm_vec_zero(&aip->big_attack_point);
14909
14910 aip->avoid_check_timestamp = timestamp(1);
14911
14912 aip->abort_rearm_timestamp = -1;
14913
14914 // artillery stuff
14915 aip->artillery_objnum = -1;
14916 aip->artillery_sig = -1;
14917
14918 // waypoint speed cap
14919 aip->waypoint_speed_cap = -1;
14920
14921 // set lethality to enemy team
14922 aip->lethality = 0.0f;
14923 aip->ai_override_flags.reset();
14924 memset(&aip->ai_override_ci,0,sizeof(control_info));
14925
14926 aip->form_obj_slotnum = -1;
14927
14928 aip->multilock_check_timestamp = timestamp(1);
14929 aip->ai_missile_locks_firing.clear();
14930 }
14931
init_ai_system()14932 void init_ai_system()
14933 {
14934 Ppfp = Path_points;
14935 }
14936
14937 //Sets the ai_info stuff based on what is in the ai class and the current ai profile
14938 //Stuff in the ai class will override what is in the ai profile, but only if it is set.
14939 //Unset per-difficulty-level values are marked with FLT_MIN or INT_MIN
14940 //Which flags are set is handled by using two flag ints: one with the flag values (TRUE/FALSE), one that
14941 //just says which flags are set.
init_aip_from_class_and_profile(ai_info * aip,ai_class * aicp,ai_profile_t * profile)14942 void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t *profile)
14943 {
14944 // since we use it so much in this function, sanity check the value for Game_skill_level
14945 if (Game_skill_level < 0 || Game_skill_level >= NUM_SKILL_LEVELS) {
14946 Warning(LOCATION, "Invalid skill level %i! Valid range 0 to %i. Resetting to default.", Game_skill_level, NUM_SKILL_LEVELS);
14947 Game_skill_level = game_get_default_skill_level();
14948 }
14949
14950 //ai_class-only stuff
14951 aip->ai_courage = aicp->ai_courage[Game_skill_level];
14952 aip->ai_patience = aicp->ai_patience[Game_skill_level];
14953 aip->ai_evasion = aicp->ai_evasion[Game_skill_level];
14954 aip->ai_accuracy = aicp->ai_accuracy[Game_skill_level];
14955
14956 aip->ai_aburn_use_factor = aicp->ai_aburn_use_factor[Game_skill_level];
14957 aip->ai_shockwave_evade_chance = aicp->ai_shockwave_evade_chance[Game_skill_level];
14958 aip->ai_get_away_chance = aicp->ai_get_away_chance[Game_skill_level];
14959 aip->ai_secondary_range_mult = aicp->ai_secondary_range_mult[Game_skill_level];
14960 aip->ai_class_autoscale = aicp->ai_class_autoscale;
14961
14962 //Apply overrides from ai class to ai profiles values
14963 //Only override values which were explicitly set in the AI class
14964 aip->ai_cmeasure_fire_chance = (aicp->ai_cmeasure_fire_chance[Game_skill_level] == FLT_MIN) ?
14965 profile->cmeasure_fire_chance[Game_skill_level] : aicp->ai_cmeasure_fire_chance[Game_skill_level];
14966 aip->ai_in_range_time = (aicp->ai_in_range_time[Game_skill_level] == FLT_MIN) ?
14967 profile->in_range_time[Game_skill_level] : aicp->ai_in_range_time[Game_skill_level];
14968 aip->ai_link_ammo_levels_maybe = (aicp->ai_link_ammo_levels_maybe[Game_skill_level] == FLT_MIN) ?
14969 profile->link_ammo_levels_maybe[Game_skill_level] : aicp->ai_link_ammo_levels_maybe[Game_skill_level];
14970 aip->ai_link_ammo_levels_always = (aicp->ai_link_ammo_levels_always[Game_skill_level] == FLT_MIN) ?
14971 profile->link_ammo_levels_always[Game_skill_level] : aicp->ai_link_ammo_levels_always[Game_skill_level];
14972 aip->ai_primary_ammo_burst_mult = (aicp->ai_primary_ammo_burst_mult[Game_skill_level] == FLT_MIN) ?
14973 profile->primary_ammo_burst_mult[Game_skill_level] : aicp->ai_primary_ammo_burst_mult[Game_skill_level];
14974 aip->ai_link_energy_levels_maybe = (aicp->ai_link_energy_levels_maybe[Game_skill_level] == FLT_MIN) ?
14975 profile->link_energy_levels_maybe[Game_skill_level] : aicp->ai_link_energy_levels_maybe[Game_skill_level];
14976 aip->ai_link_energy_levels_always = (aicp->ai_link_energy_levels_always[Game_skill_level] == FLT_MIN) ?
14977 profile->link_energy_levels_always[Game_skill_level] : aicp->ai_link_energy_levels_always[Game_skill_level];
14978 aip->ai_predict_position_delay = (aicp->ai_predict_position_delay[Game_skill_level] == INT_MIN) ?
14979 profile->predict_position_delay[Game_skill_level] : aicp->ai_predict_position_delay[Game_skill_level];
14980 aip->ai_shield_manage_delay = (aicp->ai_shield_manage_delay[Game_skill_level] == FLT_MIN) ?
14981 profile->shield_manage_delay[Game_skill_level] : aicp->ai_shield_manage_delay[Game_skill_level];
14982 aip->ai_ship_fire_delay_scale_friendly = (aicp->ai_ship_fire_delay_scale_friendly[Game_skill_level] == FLT_MIN) ?
14983 profile->ship_fire_delay_scale_friendly[Game_skill_level] : aicp->ai_ship_fire_delay_scale_friendly[Game_skill_level];
14984 aip->ai_ship_fire_delay_scale_hostile = (aicp->ai_ship_fire_delay_scale_hostile[Game_skill_level] == FLT_MIN) ?
14985 profile->ship_fire_delay_scale_hostile[Game_skill_level] : aicp->ai_ship_fire_delay_scale_hostile[Game_skill_level];
14986 aip->ai_ship_fire_secondary_delay_scale_friendly = (aicp->ai_ship_fire_secondary_delay_scale_friendly[Game_skill_level] == FLT_MIN) ?
14987 profile->ship_fire_secondary_delay_scale_friendly[Game_skill_level] : aicp->ai_ship_fire_secondary_delay_scale_friendly[Game_skill_level];
14988 aip->ai_ship_fire_secondary_delay_scale_hostile = (aicp->ai_ship_fire_secondary_delay_scale_hostile[Game_skill_level] == FLT_MIN) ?
14989 profile->ship_fire_secondary_delay_scale_hostile[Game_skill_level] : aicp->ai_ship_fire_secondary_delay_scale_hostile[Game_skill_level];
14990 aip->ai_turn_time_scale = (aicp->ai_turn_time_scale[Game_skill_level] == FLT_MIN) ?
14991 profile->turn_time_scale[Game_skill_level] : aicp->ai_turn_time_scale[Game_skill_level];
14992 aip->ai_glide_attack_percent = (aicp->ai_glide_attack_percent[Game_skill_level] == FLT_MIN) ?
14993 profile->glide_attack_percent[Game_skill_level] : aicp->ai_glide_attack_percent[Game_skill_level];
14994 aip->ai_circle_strafe_percent = (aicp->ai_circle_strafe_percent[Game_skill_level] == FLT_MIN) ?
14995 profile->circle_strafe_percent[Game_skill_level] : aicp->ai_circle_strafe_percent[Game_skill_level];
14996 aip->ai_glide_strafe_percent = (aicp->ai_glide_strafe_percent[Game_skill_level] == FLT_MIN) ?
14997 profile->glide_strafe_percent[Game_skill_level] : aicp->ai_glide_strafe_percent[Game_skill_level];
14998 aip->ai_random_sidethrust_percent = (aicp->ai_random_sidethrust_percent[Game_skill_level] == FLT_MIN) ?
14999 profile->random_sidethrust_percent[Game_skill_level] : aicp->ai_random_sidethrust_percent[Game_skill_level];
15000 aip->ai_stalemate_time_thresh = (aicp->ai_stalemate_time_thresh[Game_skill_level] == FLT_MIN) ?
15001 profile->stalemate_time_thresh[Game_skill_level] : aicp->ai_stalemate_time_thresh[Game_skill_level];
15002 aip->ai_stalemate_dist_thresh = (aicp->ai_stalemate_dist_thresh[Game_skill_level] == FLT_MIN) ?
15003 profile->stalemate_dist_thresh[Game_skill_level] : aicp->ai_stalemate_dist_thresh[Game_skill_level];
15004 aip->ai_chance_to_use_missiles_on_plr = (aicp->ai_chance_to_use_missiles_on_plr[Game_skill_level] == INT_MIN) ?
15005 profile->chance_to_use_missiles_on_plr[Game_skill_level] : aicp->ai_chance_to_use_missiles_on_plr[Game_skill_level];
15006 aip->ai_max_aim_update_delay = (aicp->ai_max_aim_update_delay[Game_skill_level] == FLT_MIN) ?
15007 profile->max_aim_update_delay[Game_skill_level] : aicp->ai_max_aim_update_delay[Game_skill_level];
15008 aip->ai_turret_max_aim_update_delay = (aicp->ai_turret_max_aim_update_delay[Game_skill_level] == FLT_MIN) ?
15009 profile->turret_max_aim_update_delay[Game_skill_level] : aicp->ai_turret_max_aim_update_delay[Game_skill_level];
15010
15011 //Combine AI profile and AI class flags
15012 aip->ai_profile_flags = profile->flags | (aicp->ai_profile_flags & aicp->ai_profile_flags_set);
15013 }
15014
ai_do_default_behavior(object * obj)15015 void ai_do_default_behavior(object *obj)
15016 {
15017 Assert(obj != NULL);
15018 Assert(obj->instance != -1);
15019 Assert(Ships[obj->instance].ai_index != -1);
15020
15021 ai_info *aip = &Ai_info[Ships[obj->instance].ai_index];
15022 ship_info* sip = &Ship_info[Ships[obj->instance].ship_info_index];
15023
15024 // default behavior in most cases (especially if we're docked) is to just stay put
15025 aip->mode = AIM_NONE;
15026 aip->submode_start_time = Missiontime;
15027 aip->active_goal = AI_GOAL_NONE;
15028
15029 // if we're not docked, we may modify the behavior a bit
15030 if (!object_is_docked(obj))
15031 {
15032 // fighters automatically chase things
15033 if (!is_instructor(obj) && (sip->is_fighter_bomber()))
15034 {
15035 int enemy_objnum = find_enemy(OBJ_INDEX(obj), 1000.0f, The_mission.ai_profile->max_attackers[Game_skill_level]);
15036 set_target_objnum(aip, enemy_objnum);
15037 aip->mode = AIM_CHASE;
15038 aip->submode = SM_ATTACK;
15039 }
15040 // support ships automatically keep a safe distance
15041 else if (sip->flags[Ship::Info_Flags::Support])
15042 {
15043 aip->mode = AIM_SAFETY;
15044 aip->submode = AISS_1;
15045 aip->ai_flags.remove(AI::AI_Flags::Repairing);
15046 }
15047 // sentry guns... do their thing
15048 else if (sip->flags[Ship::Info_Flags::Sentrygun])
15049 {
15050 aip->mode = AIM_SENTRYGUN;
15051 }
15052 }
15053 }
15054
15055 #define FRIENDLY_DAMAGE_THRESHOLD 50.0f // Display a message at this threshold. Note, this gets scaled by Skill_level
15056
15057 // send the given message from objp. called from the maybe_process_friendly_hit
15058 // code below when a message must get send to the player when he fires on friendlies
process_friendly_hit_message(int message,object * objp)15059 void process_friendly_hit_message( int message, object *objp )
15060 {
15061 int index;
15062
15063 // no traitor in multiplayer
15064 if(Game_mode & GM_MULTIPLAYER){
15065 return;
15066 }
15067
15068 // don't send this message if a player ship was hit.
15069 if ( objp->flags[Object::Object_Flags::Player_ship] ){
15070 return;
15071 }
15072
15073 // check if objp is a fighter/bomber -- if not, then find a new ship to send the message
15074 index = objp->instance;
15075 if ( !Ship_info[Ships[objp->instance].ship_info_index].is_fighter_bomber() ){
15076 index = -1;
15077 }
15078
15079 // If the ship can't send messages pick someone else
15080 if (Ships[objp->instance].flags[Ship::Ship_Flags::No_builtin_messages]) {
15081 index = -1;
15082 }
15083 if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(&Ships[objp->instance]) <= COMM_DAMAGED) {
15084 index = -1;
15085 }
15086
15087 // Karajorma - pick a random ship to send Command messages if command is silenced.
15088 if (index < 0 && (The_mission.flags[Mission::Mission_Flags::No_builtin_command]) ) {
15089 index = ship_get_random_player_wing_ship( SHIP_GET_UNSILENCED );
15090 }
15091
15092 if ( index >= 0 )
15093 {
15094 message_send_builtin_to_player( message, &Ships[index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_ANYTIME, 0, 0, -1, -1 );
15095 } else {
15096 message_send_builtin_to_player( message, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_ANYTIME, 0, 0, -1, -1 );
15097 }
15098 }
15099
15100 extern void ship_set_subsystem_strength( ship *shipp, int type, float strength );
15101
15102 /**
15103 * Object *objp_weapon, fired by *objp_hitter, hit object *objp_ship.
15104 */
maybe_process_friendly_hit(object * objp_hitter,object * objp_hit,object * objp_weapon)15105 void maybe_process_friendly_hit(object *objp_hitter, object *objp_hit, object *objp_weapon)
15106 {
15107 // no turning traitor in multiplayer
15108 if ( Game_mode & GM_MULTIPLAYER ) {
15109 return;
15110 }
15111
15112 // ditto if mission says no traitors allowed
15113 if (The_mission.flags[Mission::Mission_Flags::No_traitor]) {
15114 return;
15115 }
15116
15117 if ((obj_team(objp_hitter) == obj_team(objp_hit)) && (objp_hitter == Player_obj)) {
15118
15119 // AL 12-4-97: It is possible the Player is a OBJ_GHOST at this point. If so, bail out.
15120 if ( objp_hitter->type != OBJ_SHIP ) {
15121 return;
15122 }
15123
15124 Assert(objp_hitter->type == OBJ_SHIP);
15125 Assert(objp_hit->type == OBJ_SHIP);
15126 Assert((objp_weapon->type == OBJ_WEAPON) || (objp_weapon->type == OBJ_BEAM)); //beam added for traitor detection - FUBAR
15127
15128 ship *shipp_hitter = &Ships[objp_hitter->instance];
15129 ship *shipp_hit = &Ships[objp_hit->instance];
15130
15131 if (shipp_hitter->team != shipp_hit->team) {
15132 return;
15133 }
15134
15135 // get the player
15136 player *pp = &Players[Player_num];
15137
15138 // wacky stuff here
15139 if (pp->friendly_hits != 0) {
15140 float time_since_last_hit = f2fl(Missiontime - pp->friendly_last_hit_time);
15141 if ((time_since_last_hit >= 0.0f) && (time_since_last_hit < 10000.0f)) {
15142 if (time_since_last_hit > 60.0f) {
15143 pp->friendly_hits = 0;
15144 pp->friendly_damage = 0.0f;
15145 } else if (time_since_last_hit > 2.0f) {
15146 pp->friendly_hits -= (int) time_since_last_hit/2;
15147 pp->friendly_damage -= time_since_last_hit;
15148 }
15149
15150 if (pp->friendly_damage < 0.0f) {
15151 pp->friendly_damage = 0.0f;
15152 }
15153
15154 if (pp->friendly_hits < 0) {
15155 pp->friendly_hits = 0;
15156 }
15157 }
15158 }
15159
15160 float damage; // Damage done by weapon. Gets scaled down based on size of ship.
15161
15162 if (objp_weapon->type == OBJ_BEAM) // added beam for traitor detection -FUBAR
15163 damage = beam_get_ship_damage(&Beams[objp_weapon->instance], objp_hit);
15164 else
15165 damage = Weapon_info[Weapons[objp_weapon->instance].weapon_info_index].damage;
15166
15167 // wacky stuff here
15168 ship_info *sip = &Ship_info[Ships[objp_hit->instance].ship_info_index];
15169 if (shipp_hit->ship_max_hull_strength > 1000.0f) {
15170 float factor = shipp_hit->ship_max_hull_strength / 1000.0f;
15171 factor = MIN(100.0f, factor);
15172 damage /= factor;
15173 }
15174
15175 // Don't penalize much at all for hitting cargo
15176 if (sip->class_type > -1) {
15177 damage *= Ship_types[sip->class_type].ff_multiplier;
15178 }
15179
15180 // Hit ship, but not targeting it, so it's not so heinous, maybe an accident.
15181 if (Ai_info[shipp_hitter->ai_index].target_objnum != OBJ_INDEX(objp_hit)) {
15182 damage /= 5.0f;
15183 }
15184
15185 pp->friendly_last_hit_time = Missiontime;
15186 pp->friendly_hits++;
15187
15188 // cap damage and number of hits done this frame
15189 float accredited_damage = MIN(MAX_BURST_DAMAGE, pp->damage_this_burst + damage) - pp->damage_this_burst;
15190 pp->friendly_damage += accredited_damage;
15191 pp->damage_this_burst += accredited_damage;
15192
15193 // Done with adjustments to damage. Evaluate based on current friendly_damage
15194 nprintf(("AI", "Friendly damage: %.1f, threshold: %.1f, inc damage: %.1f, max burst: %d\n", pp->friendly_damage, FRIENDLY_DAMAGE_THRESHOLD * (1.0f + (float) (NUM_SKILL_LEVELS + 1 - Game_skill_level)/3.0f), pp->damage_this_burst, MAX_BURST_DAMAGE ));
15195
15196 if (is_instructor(objp_hit)) {
15197 // it's not nice to hit your instructor
15198 if (pp->friendly_damage > FRIENDLY_DAMAGE_THRESHOLD) {
15199 message_send_builtin_to_player( MESSAGE_INSTRUCTOR_ATTACK, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
15200 pp->last_warning_message_time = Missiontime;
15201 ship_set_subsystem_strength( Player_ship, SUBSYSTEM_WEAPONS, 0.0f);
15202
15203 training_fail();
15204
15205 // Instructor leaves.
15206 mission_do_departure(objp_hit);
15207 gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_START_FORCED ); // Force player to warp out.
15208 } else if (Missiontime - pp->last_warning_message_time > F1_0*4) {
15209 // warning every 4 sec
15210 // use NULL as the message sender here since it is the Terran Command persona
15211 message_send_builtin_to_player( MESSAGE_INSTRUCTOR_HIT, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
15212 pp->last_warning_message_time = Missiontime;
15213 }
15214
15215 // not nice to hit your friends
15216 } else if (pp->friendly_damage > FRIENDLY_DAMAGE_THRESHOLD * (1.0f + (float) (NUM_SKILL_LEVELS + 1 - Game_skill_level)/3.0f)) {
15217 process_friendly_hit_message( MESSAGE_HAMMER_SWINE, objp_hit );
15218 mission_goal_fail_all();
15219 ai_abort_rearm_request( Player_obj );
15220
15221 Player_ship->team = Iff_traitor;
15222
15223 } else if ((damage > frand()) && (Missiontime - pp->last_warning_message_time > F1_0*4) && (pp->friendly_damage > FRIENDLY_DAMAGE_THRESHOLD)) {
15224 // no closer than 4 sec intervals
15225 // Note: (damage > frand()) added on 12/9/97 by MK. Since damage is now scaled down for big ships, we could get too
15226 // many warnings. Kind of tedious. frand() returns a value in 0..1, so this won't affect legit hits.
15227 process_friendly_hit_message( MESSAGE_OOPS, objp_hit );
15228 pp->last_warning_message_time = Missiontime;
15229 }
15230 }
15231 }
15232
15233 /**
15234 * Maybe make ship with ai_info *aip attack hitter_objnum as a dynamic goal
15235 */
maybe_set_dynamic_chase(ai_info * aip,int hitter_objnum)15236 void maybe_set_dynamic_chase(ai_info *aip, int hitter_objnum)
15237 {
15238 Assert(Ship_info[Ships[aip->shipnum].ship_info_index].is_fighter_bomber());
15239
15240 // limit the number of ships attacking hitter_objnum (for now, only if hitter_objnum is player)
15241 if ( ai_maybe_limit_attackers(hitter_objnum) == 1 ) {
15242 return;
15243 }
15244
15245 // only set as target if can be targeted.
15246 if (awacs_get_level(&Objects[hitter_objnum], &Ships[aip->shipnum], 1) < 1) {
15247 return;
15248 }
15249
15250 if (aip->target_objnum != hitter_objnum)
15251 aip->aspect_locked_time = 0.0f;
15252 set_target_objnum(aip, hitter_objnum);
15253 aip->resume_goal_time = Missiontime + i2f(20); // Only chase up to 20 seconds.
15254 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
15255
15256 set_targeted_subsys(aip, NULL, -1); // Say not attacking any particular subsystem.
15257
15258 aip->previous_submode = aip->mode;
15259 aip->mode = AIM_CHASE;
15260 aip->submode = SM_ATTACK;
15261 aip->submode_start_time = Missiontime;
15262 }
15263
15264
15265 // Return true if *objp has armed an aspect seeking bomb.
15266 // This function written so a ship with an important bomb to fire will willingly take hits in the face to fire its bomb.
firing_aspect_seeking_bomb(object * objp)15267 int firing_aspect_seeking_bomb(object *objp)
15268 {
15269 ship *shipp;
15270 int bank_index;
15271 ship_weapon *swp;
15272
15273 shipp = &Ships[objp->instance];
15274
15275 swp = &shipp->weapons;
15276
15277 bank_index = swp->current_secondary_bank;
15278
15279 if (bank_index != -1) {
15280 if (swp->secondary_bank_weapons[bank_index] > 0) {
15281 if (swp->secondary_bank_ammo[bank_index] > 0) {
15282 if (Weapon_info[swp->secondary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::Bomb]) {
15283 if (Weapon_info[swp->secondary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::Homing_aspect]) {
15284 return 1;
15285 }
15286 }
15287 }
15288 }
15289 }
15290
15291 return 0;
15292 }
15293
15294 // *objp collided with big ship *big_objp at some global point.
15295 // Make it fly away from the collision point.
15296 // collision_normal is NULL, when a collision is imminent and we just want to bug out.
big_ship_collide_recover_start(object * objp,object * big_objp,vec3d * collision_normal)15297 void big_ship_collide_recover_start(object *objp, object *big_objp, vec3d *collision_normal)
15298 {
15299 ai_info *aip;
15300
15301 Assert(objp->type == OBJ_SHIP);
15302
15303 aip = &Ai_info[Ships[objp->instance].ai_index];
15304
15305 if (!timestamp_elapsed(aip->big_recover_timestamp) && (aip->ai_flags[AI::AI_Flags::Big_ship_collide_recover_1]))
15306 return;
15307
15308 if (collision_normal) {
15309 aip->big_recover_timestamp = timestamp(2000);
15310 aip->big_collision_normal = *collision_normal;
15311 } else {
15312 aip->big_recover_timestamp = timestamp(500);
15313 }
15314
15315 aip->ai_flags.remove(AI::AI_Flags::Big_ship_collide_recover_2);
15316 aip->ai_flags.set(AI::AI_Flags::Big_ship_collide_recover_1);
15317
15318 // big_recover_1_direction is 100 m out along normal
15319 if (collision_normal) {
15320 aip->big_recover_1_direction = *collision_normal * 100.f;
15321 } else {
15322 aip->big_recover_1_direction = objp->orient.vec.fvec * -100.0f;
15323 }
15324
15325 vec3d global_recover_pos_1 = aip->big_recover_1_direction + objp->pos;
15326 // go out 200 m from box closest box point
15327 get_world_closest_box_point_with_delta(&aip->big_recover_2_pos, big_objp, &global_recover_pos_1, nullptr, 300.0f);
15328
15329 accelerate_ship(aip, 0.0f);
15330 }
15331
15332 float max_lethality = 0.0f;
15333
ai_update_lethality(object * pship_obj,object * other_obj,float damage)15334 void ai_update_lethality(object *pship_obj, object *other_obj, float damage)
15335 {
15336 // Goober5000 - stop any trickle-down errors from ship_do_damage
15337 Assert(other_obj);
15338 if (!other_obj)
15339 {
15340 return;
15341 }
15342
15343 Assert(pship_obj); // Goober5000
15344 Assert(pship_obj->type == OBJ_SHIP);
15345 Assert(other_obj->type == OBJ_WEAPON || other_obj->type == OBJ_SHOCKWAVE);
15346 int dont_count = FALSE;
15347
15348 int parent = other_obj->parent;
15349 if (Objects[parent].type == OBJ_SHIP) {
15350 if (Objects[parent].signature == other_obj->parent_sig) {
15351
15352 // check damage done to enemy team
15353 if (iff_x_attacks_y(Ships[pship_obj->instance].team, Ships[Objects[parent].instance].team)) {
15354
15355 // other is weapon
15356 if (other_obj->type == OBJ_WEAPON) {
15357 weapon *wp = &Weapons[other_obj->instance];
15358 weapon_info *wif = &Weapon_info[wp->weapon_info_index];
15359
15360 // if parent is BIG|HUGE, don't count beam
15361 if ( Ship_info[Ships[Objects[parent].instance].ship_info_index].is_big_or_huge() ) {
15362 if (wif->wi_flags[Weapon::Info_Flags::Beam]) {
15363 dont_count = TRUE;
15364 }
15365 }
15366 }
15367
15368 if (!dont_count) {
15369 float lethality = 0.025f * damage; // 2 cyclops (@2000) put you at 100 lethality
15370
15371 // increase lethality weapon's parent ship
15372 ai_info *aip = &Ai_info[Ships[Objects[parent].instance].ai_index];
15373 aip->lethality += lethality;
15374 aip->lethality = MIN(110.0f, aip->lethality);
15375 // if you hit, don't be less than 0
15376 aip->lethality = MAX(0.0f, aip->lethality);
15377 }
15378 }
15379 }
15380 }
15381 }
15382
15383
15384 /**
15385 * Object *objp_ship was hit by either weapon *objp_weapon or collided into by ship hit_objp
15386 */
ai_ship_hit(object * objp_ship,object * hit_objp,vec3d * hit_normal)15387 void ai_ship_hit(object *objp_ship, object *hit_objp, vec3d *hit_normal)
15388 {
15389 int hitter_objnum = -2;
15390 object *objp_hitter = nullptr;
15391 ship *shipp;
15392 ai_info *aip, *hitter_aip;
15393
15394 shipp = &Ships[objp_ship->instance];
15395 aip = &Ai_info[shipp->ai_index];
15396
15397 if (objp_ship->flags[Object::Object_Flags::Player_ship]) {
15398 if (The_mission.ai_profile->flags[AI::Profile_Flags::Reset_last_hit_target_time_for_player_hits]) {
15399 //SUSHI: So that hitting a player ship actually resets the last_hit_target_time counter for whoever hit the player.
15400 //This is all copypasted from code below
15401 // Added OBJ_BEAM for traitor detection - FUBAR
15402 if ((hit_objp->type == OBJ_WEAPON) || (hit_objp->type == OBJ_BEAM)) {
15403 hitter_objnum = hit_objp->parent;
15404 Assert((hitter_objnum < MAX_OBJECTS));
15405 if (hitter_objnum == -1) {
15406 return; // Possible SSM, bail while we still can.
15407 }
15408 objp_hitter = &Objects[hitter_objnum];
15409 } else if (hit_objp->type == OBJ_SHIP) {
15410 objp_hitter = hit_objp;
15411 } else {
15412 UNREACHABLE("Should never happen.");
15413 return;
15414 }
15415 Assert(objp_hitter != nullptr);
15416 hitter_aip = &Ai_info[Ships[objp_hitter->instance].ai_index];
15417 hitter_aip->last_hit_target_time = Missiontime;
15418
15419 aip->last_hit_time = Missiontime;
15420 }
15421 return;
15422 }
15423
15424 if ((aip->mode == AIM_WARP_OUT) || (aip->mode == AIM_PLAY_DEAD))
15425 return;
15426
15427 if (hit_objp->type == OBJ_SHIP) {
15428 // If the object that this ship collided with is a big ship
15429 if (Ship_info[Ships[hit_objp->instance].ship_info_index].is_big_or_huge()) {
15430 // And the current object is a small ship
15431 if (Ship_info[Ships[objp_ship->instance].ship_info_index].is_small_ship()) {
15432 // Recover from hitting a big ship. Note, if two big ships collide, they just pound away at each other. Oh well. Recovery looks dumb and it's very late.
15433 big_ship_collide_recover_start(objp_ship, hit_objp, hit_normal);
15434 }
15435 }
15436 }
15437
15438 // Added OBJ_BEAM for traitor detection - FUBAR
15439 if ((hit_objp->type == OBJ_WEAPON) || (hit_objp->type == OBJ_BEAM)) {
15440 if(hit_objp->parent < 0){
15441 return;
15442 }
15443 if ( hit_objp->parent_sig != Objects[hit_objp->parent].signature ){
15444 return;
15445 }
15446
15447 weapon_info* wip = hit_objp->type == OBJ_WEAPON ? &Weapon_info[Weapons[hit_objp->instance].weapon_info_index] :
15448 &Weapon_info[Beams[hit_objp->instance].weapon_info_index];
15449 if (wip->wi_flags[Weapon::Info_Flags::Heals])
15450 return;
15451
15452 hitter_objnum = hit_objp->parent;
15453 Assert((hitter_objnum >= 0) && (hitter_objnum < MAX_OBJECTS));
15454 objp_hitter = &Objects[hitter_objnum];
15455
15456 // lets not check hits by ghosts any further either
15457 if(objp_hitter->type == OBJ_GHOST)
15458 return;
15459
15460 // Hit by a protected ship, don't attack it.
15461 if (objp_hitter->flags[Object::Object_Flags::Protected]) {
15462 if (!object_is_docked(objp_ship)) {
15463 if ((Ship_info[shipp->ship_info_index].is_fighter_bomber()) && (aip->target_objnum == -1)) {
15464 if (aip->mode == AIM_CHASE) {
15465 if (aip->submode != SM_EVADE_WEAPON) {
15466 aip->mode = AIM_CHASE;
15467 aip->submode = SM_EVADE_WEAPON;
15468 aip->submode_start_time = Missiontime;
15469 }
15470 } else if (aip->mode != AIM_EVADE_WEAPON) {
15471 aip->active_goal = AI_ACTIVE_GOAL_DYNAMIC;
15472 aip->previous_mode = aip->mode;
15473 aip->previous_submode = aip->submode;
15474 aip->mode = AIM_EVADE_WEAPON;
15475 aip->submode = -1;
15476 aip->submode_start_time = Missiontime;
15477 aip->mode_time = timestamp(MAX_EVADE_TIME); // Evade for up to five seconds.
15478 }
15479 }
15480 }
15481 return;
15482 }
15483
15484 maybe_process_friendly_hit(objp_hitter, objp_ship, hit_objp); // Deal with player's friendly fire.
15485
15486 ship_maybe_ask_for_help(shipp);
15487 } else if (hit_objp->type == OBJ_SHIP) {
15488 if (shipp->team == Ships[hit_objp->instance].team) // Don't have AI react to collisions between teammates.
15489 return;
15490 objp_hitter = hit_objp;
15491 hitter_objnum = OBJ_INDEX(hit_objp);
15492 } else {
15493 Int3(); // Hmm, what kind of object hit this if not weapon or ship? Get MikeK.
15494 return;
15495 }
15496
15497 // traitor detection was the only reason for OBJ_BEAM to make it this far.
15498 // so bail if it wasn't fired by a player.
15499 if ((hit_objp->type == OBJ_BEAM) && (!(objp_hitter->flags[Object::Object_Flags::Player_ship])))
15500 return;
15501
15502 // Collided into a protected ship, don't attack it.
15503 if (hit_objp->flags[Object::Object_Flags::Protected])
15504 return;
15505
15506 Assert(objp_hitter != nullptr);
15507 hitter_aip = &Ai_info[Ships[objp_hitter->instance].ai_index];
15508 hitter_aip->last_hit_target_time = Missiontime;
15509
15510 // store the object signature of objp_ship into ai_info, since we want to track the last ship hit by 'hitter_objnum'
15511 hitter_aip->last_objsig_hit = objp_ship->signature;
15512
15513 aip->last_hit_time = Missiontime;
15514
15515 if (aip->ai_flags[AI::AI_Flags::No_dynamic, AI::AI_Flags::Kamikaze]) // If not allowed to pursue dynamic objectives, don't evade. Dumb? Maybe change. -- MK, 3/15/98
15516 return;
15517
15518 // If this ship is awaiting repair, abort!
15519 if (aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair]) {
15520 if (get_hull_pct(objp_ship) < 0.3f) {
15521 // Note, only abort if hull below a certain level.
15522 aip->next_rearm_request_timestamp = timestamp(NEXT_REARM_TIMESTAMP/2); // Might request again after 15 seconds.
15523 if ( !(objp_ship->flags[Object::Object_Flags::Player_ship]) ) // mwa -- don't abort rearm for a player
15524 ai_abort_rearm_request(objp_ship);
15525 }
15526 }
15527
15528 // If firing a bomb, ignore enemy fire so we can gain lock drop the bomb.
15529 // Only ignore fire if aspect_locked_time > 0.5f, as this means we're in range.
15530 if (firing_aspect_seeking_bomb(objp_ship)) {
15531 if ((aip->ai_flags[AI::AI_Flags::Seek_lock]) && (aip->aspect_locked_time > 0.1f))
15532 return;
15533 }
15534
15535 // If in AIM_STRAFE mode and got hit by target, maybe attack turret if appropriate
15536 if (aip->mode == AIM_STRAFE) {
15537 Assert(hitter_objnum != -2);
15538 if (aip->target_objnum == hitter_objnum) {
15539 if ( hit_objp->type == OBJ_WEAPON ) {
15540 ai_big_strafe_maybe_attack_turret(objp_ship, hit_objp);
15541 }
15542 return;
15543 }
15544 }
15545
15546 if ((objp_ship == Player_obj) && !Player_use_ai) // Goober5000 - allow for exception
15547 return; // We don't do AI for the player.
15548
15549 maybe_update_guard_object(objp_ship, objp_hitter);
15550
15551 // Big ships don't go any further.
15552 if (!(Ship_info[shipp->ship_info_index].is_small_ship()))
15553 return;
15554
15555 // If the hitter object is the ignore object, don't attack it.
15556 ship_info *sip = &Ship_info[shipp->ship_info_index];
15557 if (is_ignore_object(aip, OBJ_INDEX(objp_hitter)) && (sip->is_fighter_bomber())) {
15558 if (!object_is_docked(objp_ship)) {
15559 if (aip->mode == AIM_NONE) {
15560 aip->mode = AIM_CHASE; // This will cause the ship to move, if not attack.
15561 aip->submode = SM_EVADE;
15562 aip->submode_start_time = Missiontime;
15563 }
15564 }
15565 return;
15566 }
15567
15568 // Maybe abort based on mode.
15569 switch (aip->mode) {
15570 case AIM_CHASE:
15571 if (aip->submode == SM_ATTACK_FOREVER)
15572 return;
15573
15574 if ( (hit_objp->type == OBJ_WEAPON) && !(aip->ai_flags[AI::AI_Flags::No_dynamic]) ) {
15575 if ( ai_big_maybe_enter_strafe_mode(objp_ship, OBJ_INDEX(hit_objp)) )
15576 return;
15577 }
15578 break;
15579 case AIM_GUARD:
15580 // If in guard mode and far away from guard object, don't pursue guy that hit me.
15581 if ((aip->guard_objnum != -1) && (aip->guard_signature == Objects[aip->guard_objnum].signature)) {
15582 if (vm_vec_dist_quick(&objp_ship->pos, &Objects[aip->guard_objnum].pos) > 500.0f) {
15583 return;
15584 }
15585 }
15586 case AIM_STILL:
15587 case AIM_STAY_NEAR:
15588 // Note: Dealt with above, at very top. case AIM_PLAY_DEAD:
15589 case AIM_STRAFE:
15590 break;
15591 case AIM_EVADE_WEAPON:
15592 case AIM_EVADE:
15593 case AIM_GET_BEHIND:
15594 case AIM_AVOID:
15595 case AIM_DOCK:
15596 case AIM_BIGSHIP:
15597 case AIM_PATH:
15598 case AIM_NONE:
15599 case AIM_BAY_DEPART:
15600 case AIM_SENTRYGUN:
15601 return;
15602 case AIM_BAY_EMERGE:
15603 // If just leaving the docking bay, don't react to enemy fire... just keep flying away from docking bay
15604 if ( (Missiontime - aip->submode_start_time) < 5*F1_0 ) {
15605 return;
15606 }
15607 break;
15608 case AIM_WAYPOINTS:
15609 if (sip->is_fighter_bomber())
15610 break;
15611 else
15612 return;
15613 break;
15614 case AIM_SAFETY:
15615 if ((aip->submode != AISS_1) || (Missiontime - aip->submode_start_time > i2f(1))) {
15616 aip->submode = AISS_1;
15617 aip->submode_start_time = Missiontime;
15618 }
15619 return;
15620 break;
15621 case AIM_WARP_OUT:
15622 return;
15623 break;
15624 default:
15625 Int3(); // Bogus mode!
15626 }
15627
15628 if (timestamp_elapsed(aip->ok_to_target_timestamp)) {
15629 aip->ai_flags.remove(AI::AI_Flags::Formation_object); // If flying in formation, bug out!
15630 aip->ai_flags.remove(AI::AI_Flags::Formation_wing);
15631 }
15632
15633 aip->hitter_objnum = hitter_objnum;
15634 aip->hitter_signature = Objects[hitter_objnum].signature;
15635
15636 // If the hitter is not on the same team as the hittee, do some stuff.
15637 if (iff_x_attacks_y(shipp->team, Ships[objp_hitter->instance].team)) {
15638
15639 if ((hitter_objnum != aip->target_objnum) && (sip->is_fighter_bomber())) {
15640 maybe_set_dynamic_chase(aip, hitter_objnum);
15641 maybe_afterburner_after_ship_hit(objp_ship, aip, &Objects[hitter_objnum]);
15642 } else {
15643 if ((aip->mode == AIM_CHASE) && ai_near_full_strength(objp_ship)) {
15644 switch (aip->submode) {
15645 case SM_ATTACK:
15646 case SM_SUPER_ATTACK:
15647 case SM_GET_AWAY:
15648 break;
15649 default:
15650 if (sip->is_fighter_bomber()) {
15651 maybe_set_dynamic_chase(aip, hitter_objnum);
15652 }
15653 maybe_afterburner_after_ship_hit(objp_ship, aip, &Objects[hitter_objnum]);
15654 break;
15655 }
15656 } else if (aip->mode == AIM_CHASE) {
15657 switch (aip->submode) {
15658 case SM_ATTACK:
15659 aip->submode = SM_EVADE;
15660 aip->submode_start_time = Missiontime;
15661 break;
15662 case SM_SUPER_ATTACK:
15663 if (Missiontime - aip->submode_start_time > i2f(1)) {
15664 aip->submode = SM_EVADE;
15665 aip->submode_start_time = Missiontime;
15666 }
15667 break;
15668 case SM_EVADE_BRAKE:
15669 break;
15670 case SM_EVADE_SQUIGGLE:
15671 aip->submode = SM_EVADE;
15672 aip->submode_start_time = Missiontime;
15673 break;
15674 default:
15675 if (sip->is_fighter_bomber()) {
15676 maybe_set_dynamic_chase(aip, hitter_objnum);
15677 maybe_afterburner_after_ship_hit(objp_ship, aip, &Objects[hitter_objnum]);
15678 }
15679
15680 break;
15681 }
15682 } else {
15683 // AL 3-15-98: Prevent escape pods from entering chase mode
15684 if (sip->is_fighter_bomber()) {
15685 maybe_set_dynamic_chase(aip, hitter_objnum);
15686 }
15687 maybe_afterburner_after_ship_hit(objp_ship, aip, &Objects[hitter_objnum]);
15688 }
15689 }
15690 }
15691 }
15692
15693 // Ship shipnum has been destroyed.
15694 // Cleanup.
ai_ship_destroy(int shipnum)15695 void ai_ship_destroy(int shipnum)
15696 {
15697 int objnum;
15698 object *other_objp;
15699 ship *shipp;
15700 ship_obj *so;
15701 ai_info *dead_aip;
15702
15703 Assert((shipnum >= 0) && (shipnum < MAX_SHIPS));
15704 Assert((Ships[shipnum].ai_index >= 0) && (Ships[shipnum].ai_index < MAX_AI_INFO));
15705 objnum = Ships[shipnum].objnum;
15706 dead_aip = &Ai_info[Ships[shipnum].ai_index];
15707
15708 // if I was getting repaired, or awaiting repair, then cleanup the repair mode. When awaiting repair, the support objnum
15709 // is -1. When the support ship is on the way, the suppoort objnum >= 0 (points to support ship).
15710 if ( dead_aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair] ) {
15711 if ( dead_aip->support_ship_objnum >= 0 )
15712 ai_do_objects_repairing_stuff( &Objects[objnum], &Objects[dead_aip->support_ship_objnum], REPAIR_INFO_END);
15713 else
15714 ai_do_objects_repairing_stuff( &Objects[objnum], NULL, REPAIR_INFO_END );
15715 }
15716
15717 // clear bay door animations
15718 ai_manage_bay_doors(&Objects[objnum], dead_aip, true);
15719
15720 // For all objects that had this ship as a target, wipe it out, forcing find of a new enemy.
15721 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
15722 other_objp = &Objects[so->objnum];
15723 Assert(other_objp->instance != -1);
15724
15725 shipp = &Ships[other_objp->instance];
15726 Assert(shipp->ai_index != -1);
15727
15728 ai_info *aip = &Ai_info[shipp->ai_index];
15729
15730 if (aip->target_objnum == objnum) {
15731 set_target_objnum(aip, -1);
15732 // If this ship had a dynamic goal of chasing the dead ship, clear the dynamic goal.
15733 if (aip->resume_goal_time != -1)
15734 aip->active_goal = AI_GOAL_NONE;
15735 }
15736
15737 if (aip->goal_objnum == objnum) {
15738 aip->goal_objnum = -1;
15739 aip->goal_signature = -1;
15740 }
15741
15742 if (aip->guard_objnum == objnum) {
15743 aip->guard_objnum = -1;
15744 aip->guard_signature = -1;
15745 }
15746
15747 if ((aip->guard_wingnum != -1) && (aip->guard_wingnum == Ai_info[Ships[Objects[objnum].instance].ai_index].wing)) {
15748 if (aip->guard_wingnum != aip->wing)
15749 ai_set_guard_wing(other_objp, aip->guard_wingnum);
15750 }
15751
15752 if (aip->hitter_objnum == objnum)
15753 aip->hitter_objnum = -1;
15754 }
15755
15756 if (dead_aip->ai_flags[AI::AI_Flags::Formation_object])
15757 ai_formation_object_recalculate_slotnums(dead_aip->goal_objnum, objnum);
15758
15759 }
15760
15761 /**
15762 * Do stuff at start of deathroll.
15763 */
ai_deathroll_start(object * dying_objp)15764 void ai_deathroll_start(object *dying_objp)
15765 {
15766 // make sure this is a ship
15767 Assert(dying_objp->type == OBJ_SHIP);
15768
15769 // mark objects we are docked with so we can do damage and separate during death roll
15770 for (dock_instance *ptr = dying_objp->dock_list; ptr != NULL; ptr = ptr->next)
15771 {
15772 object *docked_objp = ptr->docked_objp;
15773 int docker_index = ptr->dockpoint_used;
15774 int dockee_index = dock_find_dockpoint_used_by_object(docked_objp, dying_objp);
15775
15776 dock_dead_dock_objects(dying_objp, docker_index, docked_objp, dockee_index);
15777 }
15778
15779 // clean up any rearm-related stuff
15780 ai_cleanup_rearm_mode(dying_objp);
15781
15782 // clean up anybody docking or undocking to me
15783 ai_cleanup_dock_mode_objective(dying_objp);
15784
15785 // Undock from every object directly docked to dying_objp. We can't just iterate through the list because
15786 // we're undocking the objects while we iterate over them and the pointers get seriously messed up.
15787 // So we just repeatedly remove the first object until the dying object is no longer docked to anything.
15788 while (object_is_docked(dying_objp))
15789 {
15790 object *docked_objp = dock_get_first_docked_object(dying_objp);
15791
15792 // undock these objects
15793 ai_do_objects_undocked_stuff(dying_objp, docked_objp);
15794 }
15795
15796 // clear my ai mode
15797 Ai_info[Ships[dying_objp->instance].ai_index].mode = AIM_NONE;
15798 }
15799
15800 // Object *requester_objp tells rearm ship to abort rearm.
15801 // Returns true if it succeeded, else false.
15802 // To succeed means you were previously rearming.
ai_abort_rearm_request(object * requester_objp)15803 int ai_abort_rearm_request(object *requester_objp)
15804 {
15805 ship *requester_shipp;
15806 ai_info *requester_aip;
15807
15808 Assert(requester_objp->type == OBJ_SHIP);
15809 if(requester_objp->type != OBJ_SHIP){
15810 return 0;
15811 }
15812 Assert((requester_objp->instance >= 0) && (requester_objp->instance < MAX_SHIPS));
15813 if((requester_objp->instance < 0) || (requester_objp->instance >= MAX_SHIPS)){
15814 return 0;
15815 }
15816 requester_shipp = &Ships[requester_objp->instance];
15817 Assert((requester_shipp->ai_index >= 0) && (requester_shipp->ai_index < MAX_AI_INFO));
15818 if((requester_shipp->ai_index < 0) || (requester_shipp->ai_index >= MAX_AI_INFO)){
15819 return 0;
15820 }
15821 requester_aip = &Ai_info[requester_shipp->ai_index];
15822
15823 if (requester_aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair]){
15824
15825 // support objnum is always valid once a rearm repair has been requested. It points to the
15826 // ship that is coming to repair me.
15827 if (requester_aip->support_ship_objnum != -1) {
15828 object *repair_objp;
15829 ai_info *repair_aip;
15830
15831 repair_objp = &Objects[requester_aip->support_ship_objnum];
15832 repair_aip = &Ai_info[Ships[repair_objp->instance].ai_index];
15833
15834 // Make sure signatures match. This prevents nasty bugs in which an object
15835 // that was repairing another is destroyed and is replaced by another ship
15836 // before this code comes around.
15837 if (repair_objp->signature == requester_aip->support_ship_signature) {
15838
15839 Assert( repair_objp->type == OBJ_SHIP );
15840
15841 // if support ship is in the process of undocking, don't do anything.
15842 if ( repair_aip->submode < AIS_UNDOCK_0 ) {
15843 ai_do_objects_repairing_stuff( requester_objp, repair_objp, REPAIR_INFO_ABORT );
15844
15845 if ( repair_aip->submode == AIS_DOCK_4 )
15846 {
15847 repair_aip->submode = AIS_UNDOCK_0;
15848 repair_aip->submode_start_time = Missiontime;
15849 }
15850 else
15851 {
15852 // unwind all the support ship docking operations
15853 ai_cleanup_dock_mode_subjective(repair_objp);
15854 }
15855 } else {
15856 nprintf(("AI", "Not aborting rearm since already undocking\n"));
15857 }
15858 }
15859 } else {
15860 // setting these flags is the safe things to do. There may not be a corresponding repair
15861 // ship for this guys since a repair ship may be currently repairing someone else.
15862 ai_do_objects_repairing_stuff( requester_objp, NULL, REPAIR_INFO_ABORT );
15863
15864 // try and remove this guy from an arriving support ship.
15865 mission_remove_scheduled_repair(requester_objp);
15866 }
15867
15868 return 1;
15869 } else if ( requester_aip->ai_flags[AI::AI_Flags::Repairing] ) {
15870 // a support ship can request to abort when he is told to do something else (like warp out).
15871 // see if this support ships goal_objnum is valid. If so, then issue this ai_abort comment
15872 // for the ship that he is enroute to repair
15873 if ( requester_aip->goal_objnum != -1 ) {
15874 int val;
15875
15876 val = ai_abort_rearm_request( &Objects[requester_aip->goal_objnum] );
15877 return val;
15878 }
15879 }
15880
15881 return 0;
15882 }
15883
15884 // function which gets called from ai-issue_rearm_request and from code in missionparse.cpp
15885 // to actually issue the rearm goal (support_obj to rearm requester_obj);
ai_add_rearm_goal(object * requester_objp,object * support_objp)15886 void ai_add_rearm_goal( object *requester_objp, object *support_objp )
15887 {
15888 ship *support_shipp, *requester_shipp;
15889 ai_info *support_aip, *requester_aip;
15890
15891 support_shipp = &Ships[support_objp->instance];
15892 requester_shipp = &Ships[requester_objp->instance];
15893 requester_aip = &Ai_info[requester_shipp->ai_index];
15894
15895 Assert( support_shipp->ai_index != -1 );
15896 support_aip = &Ai_info[support_shipp->ai_index];
15897
15898 // if the requester is a player object, issue the order as the squadmate messaging code does. Doing so
15899 // ensures that the player get a higher priority!
15900 requester_aip->ai_flags.set(AI::AI_Flags::Awaiting_repair); // Tell that I'm awaiting repair.
15901 if ( requester_objp->flags[Object::Object_Flags::Player_ship] )
15902 ai_add_ship_goal_player( AIG_TYPE_PLAYER_SHIP, AI_GOAL_REARM_REPAIR, -1, requester_shipp->ship_name, support_aip );
15903 else
15904 ai_add_goal_ship_internal( support_aip, AI_GOAL_REARM_REPAIR, requester_shipp->ship_name, -1, -1 );
15905
15906 }
15907
15908 // Object *requester_objp requests rearming.
15909 // Returns objnum of ship coming to repair requester on success
15910 // Success means you found someone to rearm you and you weren't previously rearming.
ai_issue_rearm_request(object * requester_objp)15911 int ai_issue_rearm_request(object *requester_objp)
15912 {
15913 object *objp = NULL;
15914 ship *requester_shipp;
15915 ai_info *requester_aip;
15916
15917 Assert(requester_objp->type == OBJ_SHIP);
15918 Assert((requester_objp->instance >= 0) && (requester_objp->instance < MAX_SHIPS));
15919 requester_shipp = &Ships[requester_objp->instance];
15920
15921 Assert((requester_shipp->ai_index >= 0) && (requester_shipp->ai_index < MAX_AI_INFO));
15922 requester_aip = &Ai_info[requester_shipp->ai_index];
15923
15924 // these should have already been caught by the time we get here!
15925 Assert(!(requester_aip->ai_flags[AI::AI_Flags::Awaiting_repair]));
15926 Assert(is_support_allowed(requester_objp));
15927
15928 requester_aip->next_rearm_request_timestamp = timestamp(NEXT_REARM_TIMESTAMP); // Might request again after this much time.
15929
15930 // call ship_find_repair_ship to get a support ship. If none is found, then we will warp one in. This
15931 // function will return the next available ship which can repair requester
15932 int result = ship_find_repair_ship( requester_objp, &objp );
15933
15934 // we are able to call in a ship, or a ship is warping in
15935 // (Arriving_support_ship may be non-NULL in either of these cases, but mission_bring_in_support_ship has a check for that)
15936 if (result == 0 || result == 2) {
15937 ai_do_objects_repairing_stuff( requester_objp, NULL, REPAIR_INFO_QUEUE );
15938 mission_bring_in_support_ship( requester_objp );
15939 return -1;
15940 }
15941 // we are able to service a request
15942 else if (result == 1 || result == 3) {
15943 Assert(objp != NULL);
15944 ai_do_objects_repairing_stuff( requester_objp, objp, REPAIR_INFO_QUEUE );
15945 return OBJ_INDEX(objp);
15946 }
15947 // we aren't able to do anything!
15948 else {
15949 Assert(result == 4);
15950 UNREACHABLE("This case should have already been caught by the is_support_allowed precheck!");
15951 return -1;
15952 }
15953 }
15954
15955 /**
15956 * Make objp rearm and repair goal_objp
15957 */
ai_rearm_repair(object * objp,int docker_index,object * goal_objp,int dockee_index)15958 void ai_rearm_repair( object *objp, int docker_index, object *goal_objp, int dockee_index )
15959 {
15960 ai_info *aip, *goal_aip;
15961
15962 aip = &Ai_info[Ships[objp->instance].ai_index];
15963 aip->goal_objnum = OBJ_INDEX(goal_objp);
15964
15965 ai_dock_with_object(objp, docker_index, goal_objp, dockee_index, AIDO_DOCK);
15966 aip->ai_flags.set(AI::AI_Flags::Repairing); // Tell that repair guy is busy trying to repair someone.
15967
15968 goal_aip = &Ai_info[Ships[goal_objp->instance].ai_index];
15969
15970 goal_aip->support_ship_objnum = OBJ_INDEX(objp); // Tell which object is coming to repair.
15971 goal_aip->support_ship_signature = objp->signature;
15972
15973 ai_do_objects_repairing_stuff( goal_objp, objp, REPAIR_INFO_ONWAY );
15974
15975 goal_aip->abort_rearm_timestamp = timestamp(NEXT_REARM_TIMESTAMP*3/2);
15976 }
15977
15978 // Given a dockee object and the index of the dockbay for that object (ie the dockbay index
15979 // into polymodel->dockbays[] for the model associated with the object), return the index
15980 // of a path_num associated with than dockbay (this is an index into polymodel->paths[])
ai_return_path_num_from_dockbay(object * dockee_objp,int dockbay_index)15981 int ai_return_path_num_from_dockbay(object *dockee_objp, int dockbay_index)
15982 {
15983 if ( dockbay_index < 0 || dockee_objp == NULL ) {
15984 Int3(); // should never happen
15985 return -1;
15986 }
15987
15988 if ( dockee_objp->type == OBJ_SHIP ) {
15989 int path_num;
15990 polymodel *pm;
15991
15992 pm = model_get(Ship_info[Ships[dockee_objp->instance].ship_info_index].model_num );
15993
15994 Assert(pm->n_docks > dockbay_index);
15995 Assert(pm->docking_bays[dockbay_index].num_spline_paths > 0);
15996 Assert(pm->docking_bays[dockbay_index].splines != NULL);
15997
15998 if(pm->n_docks <= dockbay_index){
15999 return -1;
16000 }
16001 if(pm->docking_bays[dockbay_index].num_spline_paths <= 0){
16002 return -1;
16003 }
16004 if(pm->docking_bays[dockbay_index].splines == NULL){
16005 return -1;
16006 }
16007
16008 // We only need to return one path for the dockbay, so return the first
16009 path_num = pm->docking_bays[dockbay_index].splines[0];
16010 return path_num;
16011 } else {
16012 return -1;
16013 }
16014 }
16015
16016 /**
16017 * Actually go ahead and fire the synaptics.
16018 */
cheat_fire_synaptic(object * objp,ship * shipp)16019 static void cheat_fire_synaptic(object *objp, ship *shipp)
16020 {
16021 ship_weapon *swp;
16022 swp = &shipp->weapons;
16023 int current_bank = swp->current_secondary_bank;
16024 flagset<Weapon::Info_Flags> flags;
16025 flags += Weapon::Info_Flags::Spawn;
16026 ai_select_secondary_weapon(objp, swp, &flags, NULL);
16027 if (timestamp_elapsed(swp->next_secondary_fire_stamp[current_bank])) {
16028 if (ship_fire_secondary(objp)) {
16029 nprintf(("AI", "ship %s cheat fired synaptic!\n", shipp->ship_name));
16030 swp->next_secondary_fire_stamp[current_bank] = timestamp(2500);
16031 }
16032 }
16033 }
16034
16035 // For the subspace mission (sm3-09a)
16036 // for delta wing
16037 // if they're sufficiently far into the mission
16038 // if they're near one or more enemies
16039 // every so often
16040 // fire a synaptic if they have one.
maybe_cheat_fire_synaptic(object * objp)16041 void maybe_cheat_fire_synaptic(object *objp)
16042 {
16043 // Only do in subspace missions.
16044 if (!(The_mission.flags[Mission::Mission_Flags::Subspace]))
16045 return;
16046
16047 // Only do in sm3-09a
16048 if (!stricmp(Game_current_mission_filename, "sm3-09a"))
16049 {
16050 ship *shipp;
16051 int wing_index, time;
16052
16053 shipp = &Ships[objp->instance];
16054
16055 if (!(strnicmp(shipp->ship_name, NOX("delta"), 5)))
16056 {
16057 wing_index = shipp->ship_name[6] - '1';
16058
16059 if ((wing_index >= 0) && (wing_index < MAX_SHIPS_PER_WING))
16060 {
16061 time = Missiontime >> 16; // Convert to seconds.
16062 time -= 2*60; // Subtract off two minutes.
16063
16064 if (time > 0)
16065 {
16066 int modulus = 17 + wing_index*3;
16067
16068 if ((time % modulus) < 2)
16069 {
16070 int count = num_nearby_fighters(iff_get_attackee_mask(obj_team(objp)), &objp->pos, 1500.0f);
16071
16072 if (count > 0)
16073 cheat_fire_synaptic(objp, shipp);
16074 }
16075 }
16076 }
16077 }
16078 }
16079 }
16080