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, &center_objp->pos);
1680 	center_obj_z = vm_vec_dot(&sph_r_vec, &center_objp->orient.vec.fvec);
1681 
1682 	// find pt on axis with closest z
1683 	vm_vec_scale_add(&center_vec, &center_objp->pos, &center_objp->orient.vec.fvec, center_obj_z);
1684 
1685 	// get r_vec
1686 	vm_vec_sub(&r_vec, &objp->pos, &center_vec);
1687 
1688 	Assert( (vm_vec_dot(&r_vec, &center_objp->orient.vec.fvec) < 0.0001));
1689 
1690 	// get theta vec - perp to r_vec and z_vec
1691 	vm_vec_cross(&theta_vec, &center_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, &center_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, &center_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(&center, &min_vec, &max_vec);
4258 		vm_vec_normalized_dir(&vec_to_center, &center, &objp->pos);
4259 
4260 		vm_vec_scale_add(&goal_pos, &center, &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