1 /*
2 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
3 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
4 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
5 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
6 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
7 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
8 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
9 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
10 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
11 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
12 */
13 
14 #ifdef HAVE_CONFIG_H
15 #include <conf.h>
16 #endif
17 
18 #ifdef RCS
19 static char rcsid[] = "$Id: ai2.c,v 1.3 2001/10/25 02:15:55 bradleyb Exp $";
20 #endif
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <time.h>
25 
26 #include "inferno.h"
27 #include "game.h"
28 #include "mono.h"
29 #include "3d.h"
30 
31 #include "u_mem.h"
32 #include "object.h"
33 #include "render.h"
34 #include "error.h"
35 #include "ai.h"
36 #include "laser.h"
37 #include "fvi.h"
38 #include "polyobj.h"
39 #include "bm.h"
40 #include "weapon.h"
41 #include "physics.h"
42 #include "collide.h"
43 #include "player.h"
44 #include "wall.h"
45 #include "vclip.h"
46 #include "digi.h"
47 #include "fireball.h"
48 #include "morph.h"
49 #include "effects.h"
50 #include "timer.h"
51 #include "sounds.h"
52 #include "cntrlcen.h"
53 #include "multibot.h"
54 #ifdef NETWORK
55 #include "multi.h"
56 #include "network.h"
57 #endif
58 #include "gameseq.h"
59 #include "key.h"
60 #include "powerup.h"
61 #include "gauges.h"
62 #include "text.h"
63 
64 #ifdef EDITOR
65 #include "editor/editor.h"
66 #include "editor/kdefs.h"
67 #endif
68 
69 #ifndef NDEBUG
70 #include "string.h"
71 #include <time.h>
72 #endif
73 
74 void teleport_boss(object *objp);
75 int boss_fits_in_seg(object *boss_objp, int segnum);
76 
77 
78 int	Flinch_scale = 4;
79 int	Attack_scale = 24;
80 byte	Mike_to_matt_xlate[] = {AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST};
81 
82 //	Amount of time since the current robot was last processed for things such as movement.
83 //	It is not valid to use FrameTime because robots do not get moved every frame.
84 
85 int	Num_boss_teleport_segs;
86 short	Boss_teleport_segs[MAX_BOSS_TELEPORT_SEGS];
87 int	Num_boss_gate_segs;
88 short	Boss_gate_segs[MAX_BOSS_TELEPORT_SEGS];
89 
90 // ---------------------------------------------------------
91 //	On entry, N_robot_types had darn sure better be set.
92 //	Mallocs N_robot_types robot_info structs into global Robot_info.
init_ai_system(void)93 void init_ai_system(void)
94 {
95 #if 0
96 	int	i;
97 
98 	mprintf((0, "Trying to malloc %i bytes for Robot_info.\n", N_robot_types * sizeof(*Robot_info)));
99 	Robot_info = (robot_info *) d_malloc( N_robot_types * sizeof(*Robot_info) );
100 	mprintf((0, "Robot_info = %i\n", Robot_info));
101 
102 	for (i=0; i<N_robot_types; i++) {
103 		Robot_info[i].field_of_view = F1_0/2;
104 		Robot_info[i].firing_wait = F1_0;
105 		Robot_info[i].turn_time = F1_0*2;
106 		// -- Robot_info[i].fire_power = F1_0;
107 		// -- Robot_info[i].shield = F1_0/2;
108 		Robot_info[i].max_speed = F1_0*10;
109 		Robot_info[i].always_0xabcd = 0xabcd;
110 	}
111 #endif
112 
113 }
114 
115 // ---------------------------------------------------------------------------------------------------------------------
116 //	Given a behavior, set initial mode.
ai_behavior_to_mode(int behavior)117 int ai_behavior_to_mode(int behavior)
118 {
119 	switch (behavior) {
120 		case AIB_STILL:			return AIM_STILL;
121 		case AIB_NORMAL:			return AIM_CHASE_OBJECT;
122 		case AIB_BEHIND:			return AIM_BEHIND;
123 		case AIB_RUN_FROM:		return AIM_RUN_FROM_OBJECT;
124 		case AIB_SNIPE:			return AIM_STILL;	//	Changed, 09/13/95, MK, snipers are still until they see you or are hit.
125 		case AIB_STATION:			return AIM_STILL;
126 		case AIB_FOLLOW:			return AIM_FOLLOW_PATH;
127 		default:	Int3();	//	Contact Mike: Error, illegal behavior type
128 	}
129 
130 	return AIM_STILL;
131 }
132 
133 // ---------------------------------------------------------------------------------------------------------------------
134 //	Call every time the player starts a new ship.
ai_init_boss_for_ship(void)135 void ai_init_boss_for_ship(void)
136 {
137 	Boss_hit_time = -F1_0*10;
138 
139 }
140 
141 // ---------------------------------------------------------------------------------------------------------------------
142 //	initial_mode == -1 means leave mode unchanged.
init_ai_object(int objnum,int behavior,int hide_segment)143 void init_ai_object(int objnum, int behavior, int hide_segment)
144 {
145 	object	*objp = &Objects[objnum];
146 	ai_static	*aip = &objp->ctype.ai_info;
147 	ai_local		*ailp = &Ai_local_info[objnum];
148 	robot_info	*robptr = &Robot_info[objp->id];
149 
150 	if (behavior == 0) {
151 		// mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum));
152 		behavior = AIB_NORMAL;
153 		aip->behavior = behavior;
154 	}
155 	// mprintf((0, "Initializing object #%i\n", objnum));
156 
157 	//	mode is now set from the Robot dialog, so this should get overwritten.
158 	ailp->mode = AIM_STILL;
159 
160 	ailp->previous_visibility = 0;
161 
162 	if (behavior != -1) {
163 		aip->behavior = behavior;
164 		ailp->mode = ai_behavior_to_mode(aip->behavior);
165 	} else if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
166 		mprintf((0, "[obj %i -> normal] ", objnum));
167 		aip->behavior = AIB_NORMAL;
168 	}
169 
170 	if (robptr->companion) {
171 		ailp->mode = AIM_GOTO_PLAYER;
172 		Escort_kill_object = -1;
173 	}
174 
175 	if (robptr->thief) {
176 		aip->behavior = AIB_SNIPE;
177 		ailp->mode = AIM_THIEF_WAIT;
178 	}
179 
180 	if (robptr->attack_type) {
181 		aip->behavior = AIB_NORMAL;
182 		ailp->mode = ai_behavior_to_mode(aip->behavior);
183 	}
184 
185 	// This is astonishingly stupid!  This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
186 
187 	vm_vec_zero(&objp->mtype.phys_info.velocity);
188 	// -- ailp->wait_time = F1_0*5;
189 	ailp->player_awareness_time = 0;
190 	ailp->player_awareness_type = 0;
191 	aip->GOAL_STATE = AIS_SRCH;
192 	aip->CURRENT_STATE = AIS_REST;
193 	ailp->time_player_seen = GameTime;
194 	ailp->next_misc_sound_time = GameTime;
195 	ailp->time_player_sound_attacked = GameTime;
196 
197 	if ((behavior == AIB_SNIPE) || (behavior == AIB_STATION) || (behavior == AIB_RUN_FROM) || (behavior == AIB_FOLLOW)) {
198 		aip->hide_segment = hide_segment;
199 		ailp->goal_segment = hide_segment;
200 		aip->hide_index = -1;			// This means the path has not yet been created.
201 		aip->cur_path_index = 0;
202 	}
203 
204 	aip->SKIP_AI_COUNT = 0;
205 
206 	if (robptr->cloak_type == RI_CLOAKED_ALWAYS)
207 		aip->CLOAKED = 1;
208 	else
209 		aip->CLOAKED = 0;
210 
211 	objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
212 
213 	aip->REMOTE_OWNER = -1;
214 
215 	aip->dying_sound_playing = 0;
216 	aip->dying_start_time = 0;
217 
218 }
219 
220 
221 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
222 
223 // --------------------------------------------------------------------------------------------------------------------
224 //	Create a Buddy bot.
225 //	This automatically happens when you bring up the Buddy menu in a debug version.
226 //	It is available as a cheat in a non-debug (release) version.
create_buddy_bot(void)227 void create_buddy_bot(void)
228 {
229 	int	buddy_id;
230 	vms_vector	object_pos;
231 
232 	for (buddy_id=0; buddy_id<N_robot_types; buddy_id++)
233 		if (Robot_info[buddy_id].companion)
234 			break;
235 
236 	if (buddy_id == N_robot_types) {
237 		mprintf((0, "Can't create Buddy.  No 'companion' bot found in Robot_info!\n"));
238 		return;
239 	}
240 
241 	compute_segment_center(&object_pos, &Segments[ConsoleObject->segnum]);
242 
243 	create_morph_robot( &Segments[ConsoleObject->segnum], &object_pos, buddy_id);
244 }
245 
246 #define	QUEUE_SIZE	256
247 
248 // --------------------------------------------------------------------------------------------------------------------
249 //	Create list of segments boss is allowed to teleport to at segptr.
250 //	Set *num_segs.
251 //	Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
252 //	he can reach from his initial position (calls find_connected_distance).
253 //	If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
254 //	one_wall_hack added by MK, 10/13/95: A mega-hack!  Set to !0 to ignore the
init_boss_segments(short segptr[],int * num_segs,int size_check,int one_wall_hack)255 void init_boss_segments(short segptr[], int *num_segs, int size_check, int one_wall_hack)
256 {
257 	int			boss_objnum=-1;
258 	int			i;
259 
260 	*num_segs = 0;
261 #ifdef EDITOR
262 	N_selected_segs = 0;
263 #endif
264 
265 
266 if (size_check)
267 	mprintf((0, "Boss fits in segments:\n"));
268 	//	See if there is a boss.  If not, quick out.
269 	for (i=0; i<=Highest_object_index; i++)
270 		if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].boss_flag)) {
271 			if (boss_objnum != -1)		//	There are two bosses in this mine!  i and boss_objnum!
272 				Int3();			//do int3 here instead of assert so museum will work
273 			boss_objnum = i;
274 		}
275 
276 	if (boss_objnum != -1) {
277 		int			original_boss_seg;
278 		vms_vector	original_boss_pos;
279 		object		*boss_objp = &Objects[boss_objnum];
280 		int			head, tail;
281 		int			seg_queue[QUEUE_SIZE];
282 //ALREADY IN RENDER.H		byte			visited[MAX_SEGMENTS];
283 		fix			boss_size_save;
284 
285 		boss_size_save = boss_objp->size;
286 		// -- Causes problems!!	-- boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size);
287 		original_boss_seg = boss_objp->segnum;
288 		original_boss_pos = boss_objp->pos;
289 		head = 0;
290 		tail = 0;
291 		seg_queue[head++] = original_boss_seg;
292 
293 		segptr[(*num_segs)++] = original_boss_seg;
294 		mprintf((0, "%4i ", original_boss_seg));
295 		#ifdef EDITOR
296 		Selected_segs[N_selected_segs++] = original_boss_seg;
297 		#endif
298 
299 		for (i=0; i<=Highest_segment_index; i++)
300 			visited[i] = 0;
301 
302 		while (tail != head) {
303 			int		sidenum;
304 			segment	*segp = &Segments[seg_queue[tail++]];
305 
306 			tail &= QUEUE_SIZE-1;
307 
308 			for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
309 				int	w;
310 
311 				if (((w = WALL_IS_DOORWAY(segp, sidenum)) & WID_FLY_FLAG) || one_wall_hack) {
312 					//	If we get here and w == WID_WALL, then we want to process through this wall, else not.
313 					if (IS_CHILD(segp->children[sidenum])) {
314 						if (one_wall_hack)
315 							one_wall_hack--;
316 					} else
317 						continue;
318 
319 					if (visited[segp->children[sidenum]] == 0) {
320 						seg_queue[head++] = segp->children[sidenum];
321 						visited[segp->children[sidenum]] = 1;
322 						head &= QUEUE_SIZE-1;
323 						if (head > tail) {
324 							if (head == tail + QUEUE_SIZE-1)
325 								Int3();	//	queue overflow.  Make it bigger!
326 						} else
327 							if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
328 								Int3();	//	queue overflow.  Make it bigger!
329 
330 						if ((!size_check) || boss_fits_in_seg(boss_objp, segp->children[sidenum])) {
331 							segptr[(*num_segs)++] = segp->children[sidenum];
332 							if (size_check) mprintf((0, "%4i ", segp->children[sidenum]));
333 							#ifdef EDITOR
334 							Selected_segs[N_selected_segs++] = segp->children[sidenum];
335 							#endif
336 							if (*num_segs >= MAX_BOSS_TELEPORT_SEGS) {
337 								mprintf((1, "Warning: Too many boss teleport segments.  Found %i after searching %i/%i segments.\n", MAX_BOSS_TELEPORT_SEGS, segp->children[sidenum], Highest_segment_index+1));
338 								tail = head;
339 							}
340 						}
341 					}
342 				}
343 			}
344 
345 		}
346 
347 		boss_objp->size = boss_size_save;
348 		boss_objp->pos = original_boss_pos;
349 		obj_relink(boss_objnum, original_boss_seg);
350 
351 	}
352 
353 }
354 
355 extern void init_buddy_for_level(void);
356 
357 // ---------------------------------------------------------------------------------------------------------------------
init_ai_objects(void)358 void init_ai_objects(void)
359 {
360 	int	i;
361 
362 	Point_segs_free_ptr = Point_segs;
363 
364 	for (i=0; i<MAX_OBJECTS; i++) {
365 		object *objp = &Objects[i];
366 
367 		if (objp->control_type == CT_AI)
368 			init_ai_object(i, objp->ctype.ai_info.behavior, objp->ctype.ai_info.hide_segment);
369 	}
370 
371 	init_boss_segments(Boss_gate_segs, &Num_boss_gate_segs, 0, 0);
372 
373 	init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 0);
374 	if (Num_boss_teleport_segs == 1)
375 		init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 1);
376 
377 	Boss_dying_sound_playing = 0;
378 	Boss_dying = 0;
379 	// -- unused! MK, 10/21/95 -- Boss_been_hit = 0;
380 	Gate_interval = F1_0*4 - Difficulty_level*i2f(2)/3;
381 
382 	Ai_initialized = 1;
383 
384 	ai_do_cloak_stuff();
385 
386 	init_buddy_for_level();
387 
388 	if (Current_level_num == Last_level) {
389 		Boss_teleport_interval = F1_0*10;
390 		Boss_cloak_interval = F1_0*15;					//	Time between cloaks
391 	} else {
392 		Boss_teleport_interval = F1_0*7;
393 		Boss_cloak_interval = F1_0*10;					//	Time between cloaks
394 	}
395 }
396 
397 int	Lunacy = 0;
398 int	Diff_save = 1;
399 
400 fix	Firing_wait_copy[MAX_ROBOT_TYPES];
401 fix	Firing_wait2_copy[MAX_ROBOT_TYPES];
402 byte	Rapidfire_count_copy[MAX_ROBOT_TYPES];
403 
do_lunacy_on(void)404 void do_lunacy_on(void)
405 {
406 	int	i;
407 
408 	if (Lunacy)	//already on
409 		return;
410 
411 	Lunacy = 1;
412 
413 	Diff_save = Difficulty_level;
414 	Difficulty_level = NDL-1;
415 
416 	for (i=0; i<MAX_ROBOT_TYPES; i++) {
417 		Firing_wait_copy[i] = Robot_info[i].firing_wait[NDL-1];
418 		Firing_wait2_copy[i] = Robot_info[i].firing_wait2[NDL-1];
419 		Rapidfire_count_copy[i] = Robot_info[i].rapidfire_count[NDL-1];
420 
421 		Robot_info[i].firing_wait[NDL-1] = Robot_info[i].firing_wait[1];
422 		Robot_info[i].firing_wait2[NDL-1] = Robot_info[i].firing_wait2[1];
423 		Robot_info[i].rapidfire_count[NDL-1] = Robot_info[i].rapidfire_count[1];
424 	}
425 
426 }
427 
do_lunacy_off(void)428 void do_lunacy_off(void)
429 {
430 	int	i;
431 
432 	if (!Lunacy)	//already off
433 		return;
434 
435 	Lunacy = 0;
436 
437 	for (i=0; i<MAX_ROBOT_TYPES; i++) {
438 		Robot_info[i].firing_wait[NDL-1] = Firing_wait_copy[i];
439 		Robot_info[i].firing_wait2[NDL-1] = Firing_wait2_copy[i];
440 		Robot_info[i].rapidfire_count[NDL-1] = Rapidfire_count_copy[i];
441 	}
442 
443 	Difficulty_level = Diff_save;
444 }
445 
446 //	----------------------------------------------------------------
447 //	Do *dest = *delta unless:
448 //				*delta is pretty small
449 //		and	they are of different signs.
set_rotvel_and_saturate(fix * dest,fix delta)450 void set_rotvel_and_saturate(fix *dest, fix delta)
451 {
452 	if ((delta ^ *dest) < 0) {
453 		if (abs(delta) < F1_0/8) {
454 			// mprintf((0, "D"));
455 			*dest = delta/4;
456 		} else
457 			// mprintf((0, "d"));
458 			*dest = delta;
459 	} else {
460 		// mprintf((0, "!"));
461 		*dest = delta;
462 	}
463 }
464 
465 //--debug-- #ifndef NDEBUG
466 //--debug-- int	Total_turns=0;
467 //--debug-- int	Prevented_turns=0;
468 //--debug-- #endif
469 
470 #define	AI_TURN_SCALE	1
471 #define	BABY_SPIDER_ID	14
472 #define	FIRE_AT_NEARBY_PLAYER_THRESHOLD	(F1_0*40)
473 
474 extern void physics_turn_towards_vector(vms_vector *goal_vector, object *obj, fix rate);
475 extern fix Seismic_tremor_magnitude;
476 
477 //-------------------------------------------------------------------------------------------
ai_turn_towards_vector(vms_vector * goal_vector,object * objp,fix rate)478 void ai_turn_towards_vector(vms_vector *goal_vector, object *objp, fix rate)
479 {
480 	vms_vector	new_fvec;
481 	fix			dot;
482 
483 	//	Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
484 	if (rate == 0)
485 		return;
486 
487 	if ((objp->id == BABY_SPIDER_ID) && (objp->type == OBJ_ROBOT)) {
488 		physics_turn_towards_vector(goal_vector, objp, rate);
489 		return;
490 	}
491 
492 	new_fvec = *goal_vector;
493 
494 	dot = vm_vec_dot(goal_vector, &objp->orient.fvec);
495 
496 	if (dot < (F1_0 - FrameTime/2)) {
497 		fix	mag;
498 		fix	new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
499 		vm_vec_scale(&new_fvec, new_scale);
500 		vm_vec_add2(&new_fvec, &objp->orient.fvec);
501 		mag = vm_vec_normalize_quick(&new_fvec);
502 		if (mag < F1_0/256) {
503 			mprintf((1, "Degenerate vector in ai_turn_towards_vector (mag = %7.3f)\n", f2fl(mag)));
504 			new_fvec = *goal_vector;		//	if degenerate vector, go right to goal
505 		}
506 	}
507 
508 	if (Seismic_tremor_magnitude) {
509 		vms_vector	rand_vec;
510 		fix			scale;
511 		make_random_vector(&rand_vec);
512 		scale = fixdiv(2*Seismic_tremor_magnitude, Robot_info[objp->id].mass);
513 		vm_vec_scale_add2(&new_fvec, &rand_vec, scale);
514 	}
515 
516 	vm_vector_2_matrix(&objp->orient, &new_fvec, NULL, &objp->orient.rvec);
517 }
518 
519 // -- unused, 08/07/95 -- // --------------------------------------------------------------------------------------------------------------------
520 // -- unused, 08/07/95 -- void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility)
521 // -- unused, 08/07/95 -- {
522 // -- unused, 08/07/95 -- 	vms_vector	curvec;
523 // -- unused, 08/07/95 --
524 // -- unused, 08/07/95 -- // -- MK, 06/09/95	//	Random turning looks too stupid, so 1/4 of time, cheat.
525 // -- unused, 08/07/95 -- // -- MK, 06/09/95	if (previous_visibility)
526 // -- unused, 08/07/95 -- // -- MK, 06/09/95		if (d_rand() > 0x7400) {
527 // -- unused, 08/07/95 -- // -- MK, 06/09/95			ai_turn_towards_vector(vec_to_player, obj, rate);
528 // -- unused, 08/07/95 -- // -- MK, 06/09/95			return;
529 // -- unused, 08/07/95 -- // -- MK, 06/09/95		}
530 // -- unused, 08/07/95 --
531 // -- unused, 08/07/95 -- 	curvec = obj->mtype.phys_info.rotvel;
532 // -- unused, 08/07/95 --
533 // -- unused, 08/07/95 -- 	curvec.y += F1_0/64;
534 // -- unused, 08/07/95 --
535 // -- unused, 08/07/95 -- 	curvec.x += curvec.y/6;
536 // -- unused, 08/07/95 -- 	curvec.y += curvec.z/4;
537 // -- unused, 08/07/95 -- 	curvec.z += curvec.x/10;
538 // -- unused, 08/07/95 --
539 // -- unused, 08/07/95 -- 	if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
540 // -- unused, 08/07/95 -- 	if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
541 // -- unused, 08/07/95 -- 	if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
542 // -- unused, 08/07/95 --
543 // -- unused, 08/07/95 -- 	obj->mtype.phys_info.rotvel = curvec;
544 // -- unused, 08/07/95 --
545 // -- unused, 08/07/95 -- }
546 
547 //	Overall_agitation affects:
548 //		Widens field of view.  Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
549 //			Overall_agitation/128 subtracted from field of view, making robots see wider.
550 //		Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
551 //		Decreases wait between fire times by Overall_agitation/64 seconds.
552 
553 
554 // --------------------------------------------------------------------------------------------------------------------
555 //	Returns:
556 //		0		Player is not visible from object, obstruction or something.
557 //		1		Player is visible, but not in field of view.
558 //		2		Player is visible and in field of view.
559 //	Note: Uses Believed_player_pos as player's position for cloak effect.
560 //	NOTE: Will destructively modify *pos if *pos is outside the mine.
player_is_visible_from_object(object * objp,vms_vector * pos,fix field_of_view,vms_vector * vec_to_player)561 int player_is_visible_from_object(object *objp, vms_vector *pos, fix field_of_view, vms_vector *vec_to_player)
562 {
563 	fix			dot;
564 	fvi_query	fq;
565 
566 	//	Assume that robot's gun tip is in same segment as robot's center.
567 	objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_GUNSEG;
568 
569 	fq.p0						= pos;
570 	if ((pos->x != objp->pos.x) || (pos->y != objp->pos.y) || (pos->z != objp->pos.z)) {
571 		int	segnum = find_point_seg(pos, objp->segnum);
572 		if (segnum == -1) {
573 			fq.startseg = objp->segnum;
574 			*pos = objp->pos;
575 			mprintf((1, "Object %i, gun is outside mine, moving towards center.\n", objp-Objects));
576 			move_towards_segment_center(objp);
577 		} else {
578 			if (segnum != objp->segnum) {
579 				// -- mprintf((0, "Warning: Robot's gun tip not in same segment as robot center, frame %i.\n", FrameCount));
580 				objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_GUNSEG;
581 			}
582 			fq.startseg = segnum;
583 		}
584 	} else
585 		fq.startseg			= objp->segnum;
586 	fq.p1						= &Believed_player_pos;
587 	fq.rad					= F1_0/4;
588 	fq.thisobjnum			= objp-Objects;
589 	fq.ignore_obj_list	= NULL;
590 	fq.flags					= FQ_TRANSWALL; // -- Why were we checking objects? | FQ_CHECK_OBJS;		//what about trans walls???
591 
592 	Hit_type = find_vector_intersection(&fq,&Hit_data);
593 
594 	Hit_pos = Hit_data.hit_pnt;
595 	Hit_seg = Hit_data.hit_seg;
596 
597 	// -- when we stupidly checked objects -- if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) {
598 	if (Hit_type == HIT_NONE) {
599 		dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
600 		// mprintf((0, "Fvec = [%5.2f %5.2f %5.2f], vec_to_player = [%5.2f %5.2f %5.2f], dot = %7.3f\n", f2fl(objp->orient.fvec.x), f2fl(objp->orient.fvec.y), f2fl(objp->orient.fvec.z), f2fl(vec_to_player->x), f2fl(vec_to_player->y), f2fl(vec_to_player->z), f2fl(dot)));
601 		if (dot > field_of_view - (Overall_agitation << 9)) {
602 			return 2;
603 		} else {
604 			return 1;
605 		}
606 	} else {
607 		return 0;
608 	}
609 }
610 
611 // ------------------------------------------------------------------------------------------------------------------
612 //	Return 1 if animates, else return 0
do_silly_animation(object * objp)613 int do_silly_animation(object *objp)
614 {
615 	int				objnum = objp-Objects;
616 	jointpos 		*jp_list;
617 	int				robot_type, gun_num, robot_state, num_joint_positions;
618 	polyobj_info	*pobj_info = &objp->rtype.pobj_info;
619 	ai_static		*aip = &objp->ctype.ai_info;
620 	// ai_local			*ailp = &Ai_local_info[objnum];
621 	int				num_guns, at_goal;
622 	int				attack_type;
623 	int				flinch_attack_scale = 1;
624 
625 	robot_type = objp->id;
626 	num_guns = Robot_info[robot_type].n_guns;
627 	attack_type = Robot_info[robot_type].attack_type;
628 
629 	if (num_guns == 0) {
630 		// mprintf((0, "Object #%i of type #%i has 0 guns.\n", objp-Objects, robot_type));
631 		return 0;
632 	}
633 
634 	//	This is a hack.  All positions should be based on goal_state, not GOAL_STATE.
635 	robot_state = Mike_to_matt_xlate[aip->GOAL_STATE];
636 	// previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
637 
638 	if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
639 		flinch_attack_scale = Attack_scale;
640 	else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
641 		flinch_attack_scale = Flinch_scale;
642 
643 	at_goal = 1;
644 	for (gun_num=0; gun_num <= num_guns; gun_num++) {
645 		int	joint;
646 
647 		num_joint_positions = robot_get_anim_state(&jp_list, robot_type, gun_num, robot_state);
648 
649 		for (joint=0; joint<num_joint_positions; joint++) {
650 			fix			delta_angle, delta_2;
651 			int			jointnum = jp_list[joint].jointnum;
652 			vms_angvec	*jp = &jp_list[joint].angles;
653 			vms_angvec	*pobjp = &pobj_info->anim_angles[jointnum];
654 
655 			if (jointnum >= Polygon_models[objp->rtype.pobj_info.model_num].n_models) {
656 				Int3();		// Contact Mike: incompatible data, illegal jointnum, problem in pof file?
657 				continue;
658 			}
659 			if (jp->p != pobjp->p) {
660 				if (gun_num == 0)
661 					at_goal = 0;
662 				Ai_local_info[objnum].goal_angles[jointnum].p = jp->p;
663 
664 				delta_angle = jp->p - pobjp->p;
665 				if (delta_angle >= F1_0/2)
666 					delta_2 = -ANIM_RATE;
667 				else if (delta_angle >= 0)
668 					delta_2 = ANIM_RATE;
669 				else if (delta_angle >= -F1_0/2)
670 					delta_2 = -ANIM_RATE;
671 				else
672 					delta_2 = ANIM_RATE;
673 
674 				if (flinch_attack_scale != 1)
675 					delta_2 *= flinch_attack_scale;
676 
677 				Ai_local_info[objnum].delta_angles[jointnum].p = delta_2/DELTA_ANG_SCALE;		// complete revolutions per second
678 			}
679 
680 			if (jp->b != pobjp->b) {
681 				if (gun_num == 0)
682 					at_goal = 0;
683 				Ai_local_info[objnum].goal_angles[jointnum].b = jp->b;
684 
685 				delta_angle = jp->b - pobjp->b;
686 				if (delta_angle >= F1_0/2)
687 					delta_2 = -ANIM_RATE;
688 				else if (delta_angle >= 0)
689 					delta_2 = ANIM_RATE;
690 				else if (delta_angle >= -F1_0/2)
691 					delta_2 = -ANIM_RATE;
692 				else
693 					delta_2 = ANIM_RATE;
694 
695 				if (flinch_attack_scale != 1)
696 					delta_2 *= flinch_attack_scale;
697 
698 				Ai_local_info[objnum].delta_angles[jointnum].b = delta_2/DELTA_ANG_SCALE;		// complete revolutions per second
699 			}
700 
701 			if (jp->h != pobjp->h) {
702 				if (gun_num == 0)
703 					at_goal = 0;
704 				Ai_local_info[objnum].goal_angles[jointnum].h = jp->h;
705 
706 				delta_angle = jp->h - pobjp->h;
707 				if (delta_angle >= F1_0/2)
708 					delta_2 = -ANIM_RATE;
709 				else if (delta_angle >= 0)
710 					delta_2 = ANIM_RATE;
711 				else if (delta_angle >= -F1_0/2)
712 					delta_2 = -ANIM_RATE;
713 				else
714 					delta_2 = ANIM_RATE;
715 
716 				if (flinch_attack_scale != 1)
717 					delta_2 *= flinch_attack_scale;
718 
719 				Ai_local_info[objnum].delta_angles[jointnum].h = delta_2/DELTA_ANG_SCALE;		// complete revolutions per second
720 			}
721 		}
722 
723 		if (at_goal) {
724 			//ai_static	*aip = &objp->ctype.ai_info;
725 			ai_local		*ailp = &Ai_local_info[objp-Objects];
726 			ailp->achieved_state[gun_num] = ailp->goal_state[gun_num];
727 			if (ailp->achieved_state[gun_num] == AIS_RECO)
728 				ailp->goal_state[gun_num] = AIS_FIRE;
729 
730 			if (ailp->achieved_state[gun_num] == AIS_FLIN)
731 				ailp->goal_state[gun_num] = AIS_LOCK;
732 
733 		}
734 	}
735 
736 	if (at_goal == 1) //num_guns)
737 		aip->CURRENT_STATE = aip->GOAL_STATE;
738 
739 	return 1;
740 }
741 
742 //	------------------------------------------------------------------------------------------
743 //	Move all sub-objects in an object towards their goals.
744 //	Current orientation of object is at:	pobj_info.anim_angles
745 //	Goal orientation of object is at:		ai_info.goal_angles
746 //	Delta orientation of object is at:		ai_info.delta_angles
ai_frame_animation(object * objp)747 void ai_frame_animation(object *objp)
748 {
749 	int	objnum = objp-Objects;
750 	int	joint;
751 	int	num_joints;
752 
753 	num_joints = Polygon_models[objp->rtype.pobj_info.model_num].n_models;
754 
755 	for (joint=1; joint<num_joints; joint++) {
756 		fix			delta_to_goal;
757 		fix			scaled_delta_angle;
758 		vms_angvec	*curangp = &objp->rtype.pobj_info.anim_angles[joint];
759 		vms_angvec	*goalangp = &Ai_local_info[objnum].goal_angles[joint];
760 		vms_angvec	*deltaangp = &Ai_local_info[objnum].delta_angles[joint];
761 
762 		delta_to_goal = goalangp->p - curangp->p;
763 		if (delta_to_goal > 32767)
764 			delta_to_goal = delta_to_goal - 65536;
765 		else if (delta_to_goal < -32767)
766 			delta_to_goal = 65536 + delta_to_goal;
767 
768 		if (delta_to_goal) {
769 			scaled_delta_angle = fixmul(deltaangp->p, FrameTime) * DELTA_ANG_SCALE;
770 			curangp->p += scaled_delta_angle;
771 			if (abs(delta_to_goal) < abs(scaled_delta_angle))
772 				curangp->p = goalangp->p;
773 		}
774 
775 		delta_to_goal = goalangp->b - curangp->b;
776 		if (delta_to_goal > 32767)
777 			delta_to_goal = delta_to_goal - 65536;
778 		else if (delta_to_goal < -32767)
779 			delta_to_goal = 65536 + delta_to_goal;
780 
781 		if (delta_to_goal) {
782 			scaled_delta_angle = fixmul(deltaangp->b, FrameTime) * DELTA_ANG_SCALE;
783 			curangp->b += scaled_delta_angle;
784 			if (abs(delta_to_goal) < abs(scaled_delta_angle))
785 				curangp->b = goalangp->b;
786 		}
787 
788 		delta_to_goal = goalangp->h - curangp->h;
789 		if (delta_to_goal > 32767)
790 			delta_to_goal = delta_to_goal - 65536;
791 		else if (delta_to_goal < -32767)
792 			delta_to_goal = 65536 + delta_to_goal;
793 
794 		if (delta_to_goal) {
795 			scaled_delta_angle = fixmul(deltaangp->h, FrameTime) * DELTA_ANG_SCALE;
796 			curangp->h += scaled_delta_angle;
797 			if (abs(delta_to_goal) < abs(scaled_delta_angle))
798 				curangp->h = goalangp->h;
799 		}
800 
801 	}
802 
803 }
804 
805 // ----------------------------------------------------------------------------------
set_next_fire_time(object * objp,ai_local * ailp,robot_info * robptr,int gun_num)806 void set_next_fire_time(object *objp, ai_local *ailp, robot_info *robptr, int gun_num)
807 {
808 	//	For guys in snipe mode, they have a 50% shot of getting this shot in free.
809 	if ((gun_num != 0) || (robptr->weapon_type2 == -1))
810 		if ((objp->ctype.ai_info.behavior != AIB_SNIPE) || (d_rand() > 16384))
811 			ailp->rapidfire_count++;
812 
813 	//	Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
814 // -- 	if (((robptr->weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
815 // -- 		ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
816 // -- 	} else {
817 // -- 		if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
818 // -- 			ailp->rapidfire_count = 0;
819 // -- 			ailp->next_fire = robptr->firing_wait[Difficulty_level];
820 // -- 		} else
821 // -- 			ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
822 // -- 	}
823 
824 	if (((gun_num != 0) || (robptr->weapon_type2 == -1)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
825 		ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
826 	} else {
827 		if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
828 			ailp->next_fire = robptr->firing_wait[Difficulty_level];
829 			if (ailp->rapidfire_count >= robptr->rapidfire_count[Difficulty_level])
830 				ailp->rapidfire_count = 0;
831 		} else
832 			ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
833 	}
834 }
835 
836 // ----------------------------------------------------------------------------------
837 //	When some robots collide with the player, they attack.
838 //	If player is cloaked, then robot probably didn't actually collide, deal with that here.
do_ai_robot_hit_attack(object * robot,object * playerobj,vms_vector * collision_point)839 void do_ai_robot_hit_attack(object *robot, object *playerobj, vms_vector *collision_point)
840 {
841 	ai_local		*ailp = &Ai_local_info[robot-Objects];
842 	robot_info *robptr = &Robot_info[robot->id];
843 
844 //#ifndef NDEBUG
845 	if (!Robot_firing_enabled)
846 		return;
847 //#endif
848 
849 	//	If player is dead, stop firing.
850 	if (Objects[Players[Player_num].objnum].type == OBJ_GHOST)
851 		return;
852 
853 	if (robptr->attack_type == 1) {
854 		if (ailp->next_fire <= 0) {
855 			if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
856 				if (vm_vec_dist_quick(&ConsoleObject->pos, &robot->pos) < robot->size + ConsoleObject->size + F1_0*2) {
857 					collide_player_and_nasty_robot( playerobj, robot, collision_point );
858 					if (robptr->energy_drain && Players[Player_num].energy) {
859 						Players[Player_num].energy -= robptr->energy_drain * F1_0;
860 						if (Players[Player_num].energy < 0)
861 							Players[Player_num].energy = 0;
862 						// -- unused, use claw_sound in bitmaps.tbl -- digi_link_sound_to_pos( SOUND_ROBOT_SUCKED_PLAYER, playerobj->segnum, 0, collision_point, 0, F1_0 );
863 					}
864 				}
865 
866 			robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
867 			set_next_fire_time(robot, ailp, robptr, 1);	//	1 = gun_num: 0 is special (uses next_fire2)
868 		}
869 	}
870 
871 }
872 
873 #ifndef _OBJECT_H
874 extern int Player_exploded;
875 #endif
876 
877 #define	FIRE_K	8		//	Controls average accuracy of robot firing.  Smaller numbers make firing worse.  Being power of 2 doesn't matter.
878 
879 // ====================================================================================================================
880 
881 #define	MIN_LEAD_SPEED		(F1_0*4)
882 #define	MAX_LEAD_DISTANCE	(F1_0*200)
883 #define	LEAD_RANGE			(F1_0/2)
884 
885 // --------------------------------------------------------------------------------------------------------------------
886 //	Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
compute_lead_component(fix player_pos,fix robot_pos,fix player_vel,fix elapsed_time)887 fix compute_lead_component(fix player_pos, fix robot_pos, fix player_vel, fix elapsed_time)
888 {
889 	return fixdiv(player_pos - robot_pos, elapsed_time) + player_vel;
890 }
891 
892 // --------------------------------------------------------------------------------------------------------------------
893 //	Lead the player, returning point to fire at in fire_point.
894 //	Rules:
895 //		Player not cloaked
896 //		Player must be moving at a speed >= MIN_LEAD_SPEED
897 //		Player not farther away than MAX_LEAD_DISTANCE
898 //		dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
899 //		if firing a matter weapon, less leading, based on skill level.
lead_player(object * objp,vms_vector * fire_point,vms_vector * believed_player_pos,int gun_num,vms_vector * fire_vec)900 int lead_player(object *objp, vms_vector *fire_point, vms_vector *believed_player_pos, int gun_num, vms_vector *fire_vec)
901 {
902 	fix			dot, player_speed, dist_to_player, max_weapon_speed, projected_time;
903 	vms_vector	player_movement_dir, vec_to_player;
904 	int			weapon_type;
905 	weapon_info	*wptr;
906 	robot_info	*robptr;
907 
908 	if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)
909 		return 0;
910 
911 	player_movement_dir = ConsoleObject->mtype.phys_info.velocity;
912 	player_speed = vm_vec_normalize_quick(&player_movement_dir);
913 
914 	if (player_speed < MIN_LEAD_SPEED)
915 		return 0;
916 
917 	vm_vec_sub(&vec_to_player, believed_player_pos, fire_point);
918 	dist_to_player = vm_vec_normalize_quick(&vec_to_player);
919 	if (dist_to_player > MAX_LEAD_DISTANCE)
920 		return 0;
921 
922 	dot = vm_vec_dot(&vec_to_player, &player_movement_dir);
923 
924 	if ((dot < -LEAD_RANGE) || (dot > LEAD_RANGE))
925 		return 0;
926 
927 	//	Looks like it might be worth trying to lead the player.
928 	robptr = &Robot_info[objp->id];
929 	weapon_type = robptr->weapon_type;
930 	if (robptr->weapon_type2 != -1)
931 		if (gun_num == 0)
932 			weapon_type = robptr->weapon_type2;
933 
934 	wptr = &Weapon_info[weapon_type];
935 	max_weapon_speed = wptr->speed[Difficulty_level];
936 	if (max_weapon_speed < F1_0)
937 		return 0;
938 
939 	//	Matter weapons:
940 	//	At Rookie or Trainee, don't lead at all.
941 	//	At higher skill levels, don't lead as well.  Accomplish this by screwing up max_weapon_speed.
942 	if (wptr->matter)
943 	{
944 		if (Difficulty_level <= 1)
945 			return 0;
946 		else
947 			max_weapon_speed *= (NDL-Difficulty_level);
948 	}
949 
950 	projected_time = fixdiv(dist_to_player, max_weapon_speed);
951 
952 	fire_vec->x = compute_lead_component(believed_player_pos->x, fire_point->x, ConsoleObject->mtype.phys_info.velocity.x, projected_time);
953 	fire_vec->y = compute_lead_component(believed_player_pos->y, fire_point->y, ConsoleObject->mtype.phys_info.velocity.y, projected_time);
954 	fire_vec->z = compute_lead_component(believed_player_pos->z, fire_point->z, ConsoleObject->mtype.phys_info.velocity.z, projected_time);
955 
956 	vm_vec_normalize_quick(fire_vec);
957 
958 	Assert(vm_vec_dot(fire_vec, &objp->orient.fvec) < 3*F1_0/2);
959 
960 	//	Make sure not firing at especially strange angle.  If so, try to correct.  If still bad, give up after one try.
961 	if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
962 		vm_vec_add2(fire_vec, &vec_to_player);
963 		vm_vec_scale(fire_vec, F1_0/2);
964 		if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
965 			return 0;
966 		}
967 	}
968 
969 	return 1;
970 }
971 
972 // --------------------------------------------------------------------------------------------------------------------
973 //	Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
974 //	center of the robot will not fire right at the player.  We need to aim the guns at the player.  Barring that, we cheat.
975 //	When this routine is complete, the parameter vec_to_player should not be necessary.
ai_fire_laser_at_player(object * obj,vms_vector * fire_point,int gun_num,vms_vector * believed_player_pos)976 void ai_fire_laser_at_player(object *obj, vms_vector *fire_point, int gun_num, vms_vector *believed_player_pos)
977 {
978 	int			objnum = obj-Objects;
979 	ai_local		*ailp = &Ai_local_info[objnum];
980 	robot_info	*robptr = &Robot_info[obj->id];
981 	vms_vector	fire_vec;
982 	vms_vector	bpp_diff;
983 	int			weapon_type;
984 	fix			aim, dot;
985 	int			count;
986 
987 	Assert(robptr->attack_type == 0);	//	We should never be coming here for the green guy, as he has no laser!
988 
989 	//	If this robot is only awake because a camera woke it up, don't fire.
990 	if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE)
991 		return;
992 
993 	if (!Robot_firing_enabled)
994 		return;
995 
996 	if (obj->control_type == CT_MORPH)
997 		return;
998 
999 	//	If player is exploded, stop firing.
1000 	if (Player_exploded)
1001 		return;
1002 
1003 	if (obj->ctype.ai_info.dying_start_time)
1004 		return;		//	No firing while in death roll.
1005 
1006 	//	Don't let the boss fire while in death roll.  Sorry, this is the easiest way to do this.
1007 	//	If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
1008 	if (Boss_dying_start_time & Robot_info[obj->id].boss_flag)
1009 		return;
1010 
1011 	//	If player is cloaked, maybe don't fire based on how long cloaked and randomness.
1012 	if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
1013 		fix	cloak_time = Ai_cloak_info[objnum % MAX_AI_CLOAK_INFO].last_time;
1014 
1015 		if (GameTime - cloak_time > CLOAK_TIME_MAX/4)
1016 			if (d_rand() > fixdiv(GameTime - cloak_time, CLOAK_TIME_MAX)/2) {
1017 				set_next_fire_time(obj, ailp, robptr, gun_num);
1018 				return;
1019 			}
1020 	}
1021 
1022 	//	Handle problem of a robot firing through a wall because its gun tip is on the other
1023 	//	side of the wall than the robot's center.  For speed reasons, we normally only compute
1024 	//	the vector from the gun point to the player.  But we need to know whether the gun point
1025 	//	is separated from the robot's center by a wall.  If so, don't fire!
1026 	if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_GUNSEG) {
1027 		//	Well, the gun point is in a different segment than the robot's center.
1028 		//	This is almost always ok, but it is not ok if something solid is in between.
1029 		int	conn_side;
1030 		int	gun_segnum = find_point_seg(fire_point, obj->segnum);
1031 
1032 		//	See if these segments are connected, which should almost always be the case.
1033 		conn_side = find_connect_side(&Segments[gun_segnum], &Segments[obj->segnum]);
1034 		if (conn_side != -1) {
1035 			//	They are connected via conn_side in segment obj->segnum.
1036 			//	See if they are unobstructed.
1037 			if (!(WALL_IS_DOORWAY(&Segments[obj->segnum], conn_side) & WID_FLY_FLAG)) {
1038 				//	Can't fly through, so don't let this bot fire through!
1039 				return;
1040 			}
1041 		} else {
1042 			//	Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
1043 			fvi_query	fq;
1044 			fvi_info		hit_data;
1045 			int			fate;
1046 
1047 			fq.startseg				= obj->segnum;
1048 			fq.p0						= &obj->pos;
1049 			fq.p1						= fire_point;
1050 			fq.rad					= 0;
1051 			fq.thisobjnum			= obj-Objects;
1052 			fq.ignore_obj_list	= NULL;
1053 			fq.flags					= FQ_TRANSWALL;
1054 
1055 			fate = find_vector_intersection(&fq, &hit_data);
1056 			if (fate != HIT_NONE) {
1057 				Int3();		//	This bot's gun is poking through a wall, so don't fire.
1058 				move_towards_segment_center(obj);		//	And decrease chances it will happen again.
1059 				return;
1060 			}
1061 		}
1062 	}
1063 
1064 	// -- mprintf((0, "Firing from gun #%i at time = %7.3f\n", gun_num, f2fl(GameTime)));
1065 
1066 	//	Set position to fire at based on difficulty level and robot's aiming ability
1067 	aim = FIRE_K*F1_0 - (FIRE_K-1)*(robptr->aim << 8);	//	F1_0 in bitmaps.tbl = same as used to be.  Worst is 50% more error.
1068 
1069 	//	Robots aim more poorly during seismic disturbance.
1070 	if (Seismic_tremor_magnitude) {
1071 		fix	temp;
1072 
1073 		temp = F1_0 - abs(Seismic_tremor_magnitude);
1074 		if (temp < F1_0/2)
1075 			temp = F1_0/2;
1076 
1077 		aim = fixmul(aim, temp);
1078 	}
1079 
1080 	//	Lead the player half the time.
1081 	//	Note that when leading the player, aim is perfect.  This is probably acceptable since leading is so hacked in.
1082 	//	Problem is all robots will lead equally badly.
1083 	if (d_rand() < 16384) {
1084 		if (lead_player(obj, fire_point, believed_player_pos, gun_num, &fire_vec))		//	Stuff direction to fire at in fire_point.
1085 			goto player_led;
1086 	}
1087 
1088 	dot = 0;
1089 	count = 0;			//	Don't want to sit in this loop forever...
1090 	while ((count < 4) && (dot < F1_0/4)) {
1091 		bpp_diff.x = believed_player_pos->x + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1092 		bpp_diff.y = believed_player_pos->y + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1093 		bpp_diff.z = believed_player_pos->z + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1094 
1095 		vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point);
1096 		dot = vm_vec_dot(&obj->orient.fvec, &fire_vec);
1097 		count++;
1098 	}
1099 player_led: ;
1100 
1101 	weapon_type = robptr->weapon_type;
1102 	if (robptr->weapon_type2 != -1)
1103 		if (gun_num == 0)
1104 			weapon_type = robptr->weapon_type2;
1105 
1106 	Laser_create_new_easy( &fire_vec, fire_point, obj-Objects, weapon_type, 1);
1107 
1108 #ifdef NETWORK
1109 	if (Game_mode & GM_MULTI) {
1110 		ai_multi_send_robot_position(objnum, -1);
1111 		multi_send_robot_fire(objnum, obj->ctype.ai_info.CURRENT_GUN, &fire_vec);
1112 	}
1113 #endif
1114 
1115 	create_awareness_event(obj, PA_NEARBY_ROBOT_FIRED);
1116 
1117 	set_next_fire_time(obj, ailp, robptr, gun_num);
1118 
1119 }
1120 
1121 // --------------------------------------------------------------------------------------------------------------------
1122 //	vec_goal must be normalized, or close to it.
1123 //	if dot_based set, then speed is based on direction of movement relative to heading
move_towards_vector(object * objp,vms_vector * vec_goal,int dot_based)1124 void move_towards_vector(object *objp, vms_vector *vec_goal, int dot_based)
1125 {
1126 	physics_info	*pptr = &objp->mtype.phys_info;
1127 	fix				speed, dot, max_speed;
1128 	robot_info		*robptr = &Robot_info[objp->id];
1129 	vms_vector		vel;
1130 
1131 	//	Trying to move towards player.  If forward vector much different than velocity vector,
1132 	//	bash velocity vector twice as much towards player as usual.
1133 
1134 	vel = pptr->velocity;
1135 	vm_vec_normalize_quick(&vel);
1136 	dot = vm_vec_dot(&vel, &objp->orient.fvec);
1137 
1138 	if (robptr->thief)
1139 		dot = (F1_0+dot)/2;
1140 
1141 	if (dot_based && (dot < 3*F1_0/4)) {
1142 		//	This funny code is supposed to slow down the robot and move his velocity towards his direction
1143 		//	more quickly than the general code
1144 		pptr->velocity.x = pptr->velocity.x/2 + fixmul(vec_goal->x, FrameTime*32);
1145 		pptr->velocity.y = pptr->velocity.y/2 + fixmul(vec_goal->y, FrameTime*32);
1146 		pptr->velocity.z = pptr->velocity.z/2 + fixmul(vec_goal->z, FrameTime*32);
1147 	} else {
1148 		pptr->velocity.x += fixmul(vec_goal->x, FrameTime*64) * (Difficulty_level+5)/4;
1149 		pptr->velocity.y += fixmul(vec_goal->y, FrameTime*64) * (Difficulty_level+5)/4;
1150 		pptr->velocity.z += fixmul(vec_goal->z, FrameTime*64) * (Difficulty_level+5)/4;
1151 	}
1152 
1153 	speed = vm_vec_mag_quick(&pptr->velocity);
1154 	max_speed = robptr->max_speed[Difficulty_level];
1155 
1156 	//	Green guy attacks twice as fast as he moves away.
1157 	if ((robptr->attack_type == 1) || robptr->thief || robptr->kamikaze)
1158 		max_speed *= 2;
1159 
1160 	if (speed > max_speed) {
1161 		pptr->velocity.x = (pptr->velocity.x*3)/4;
1162 		pptr->velocity.y = (pptr->velocity.y*3)/4;
1163 		pptr->velocity.z = (pptr->velocity.z*3)/4;
1164 	}
1165 }
1166 
1167 // --------------------------------------------------------------------------------------------------------------------
move_towards_player(object * objp,vms_vector * vec_to_player)1168 void move_towards_player(object *objp, vms_vector *vec_to_player)
1169 //	vec_to_player must be normalized, or close to it.
1170 {
1171 	move_towards_vector(objp, vec_to_player, 1);
1172 }
1173 
1174 // --------------------------------------------------------------------------------------------------------------------
1175 //	I am ashamed of this: fast_flag == -1 means normal slide about.  fast_flag = 0 means no evasion.
move_around_player(object * objp,vms_vector * vec_to_player,int fast_flag)1176 void move_around_player(object *objp, vms_vector *vec_to_player, int fast_flag)
1177 {
1178 	physics_info	*pptr = &objp->mtype.phys_info;
1179 	fix				speed;
1180 	robot_info		*robptr = &Robot_info[objp->id];
1181 	int				objnum = objp-Objects;
1182 	int				dir;
1183 	int				dir_change;
1184 	fix				ft;
1185 	vms_vector		evade_vector;
1186 	int				count=0;
1187 
1188 	if (fast_flag == 0)
1189 		return;
1190 
1191 	dir_change = 48;
1192 	ft = FrameTime;
1193 	if (ft < F1_0/32) {
1194 		dir_change *= 8;
1195 		count += 3;
1196 	} else
1197 		while (ft < F1_0/4) {
1198 			dir_change *= 2;
1199 			ft *= 2;
1200 			count++;
1201 		}
1202 
1203 	dir = (FrameCount + (count+1) * (objnum*8 + objnum*4 + objnum)) & dir_change;
1204 	dir >>= (4+count);
1205 
1206 	Assert((dir >= 0) && (dir <= 3));
1207 
1208 	switch (dir) {
1209 		case 0:
1210 			evade_vector.x = fixmul(vec_to_player->z, FrameTime*32);
1211 			evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1212 			evade_vector.z = fixmul(-vec_to_player->x, FrameTime*32);
1213 			break;
1214 		case 1:
1215 			evade_vector.x = fixmul(-vec_to_player->z, FrameTime*32);
1216 			evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1217 			evade_vector.z = fixmul(vec_to_player->x, FrameTime*32);
1218 			break;
1219 		case 2:
1220 			evade_vector.x = fixmul(-vec_to_player->y, FrameTime*32);
1221 			evade_vector.y = fixmul(vec_to_player->x, FrameTime*32);
1222 			evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
1223 			break;
1224 		case 3:
1225 			evade_vector.x = fixmul(vec_to_player->y, FrameTime*32);
1226 			evade_vector.y = fixmul(-vec_to_player->x, FrameTime*32);
1227 			evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
1228 			break;
1229 		default:
1230 			Error("Function move_around_player: Bad case.");
1231 	}
1232 
1233 	//	Note: -1 means normal circling about the player.  > 0 means fast evasion.
1234 	if (fast_flag > 0) {
1235 		fix	dot;
1236 
1237 		//	Only take evasive action if looking at player.
1238 		//	Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
1239 
1240 		dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
1241 		if ((dot > robptr->field_of_view[Difficulty_level]) && !(ConsoleObject->flags & PLAYER_FLAGS_CLOAKED)) {
1242 			fix	damage_scale;
1243 
1244 			if (robptr->strength)
1245 				damage_scale = fixdiv(objp->shields, robptr->strength);
1246 			else
1247 				damage_scale = F1_0;
1248 			if (damage_scale > F1_0)
1249 				damage_scale = F1_0;		//	Just in case...
1250 			else if (damage_scale < 0)
1251 				damage_scale = 0;			//	Just in case...
1252 
1253 			vm_vec_scale(&evade_vector, i2f(fast_flag) + damage_scale);
1254 		}
1255 	}
1256 
1257 	pptr->velocity.x += evade_vector.x;
1258 	pptr->velocity.y += evade_vector.y;
1259 	pptr->velocity.z += evade_vector.z;
1260 
1261 	speed = vm_vec_mag_quick(&pptr->velocity);
1262 	if ((objp-Objects != 1) && (speed > robptr->max_speed[Difficulty_level])) {
1263 		pptr->velocity.x = (pptr->velocity.x*3)/4;
1264 		pptr->velocity.y = (pptr->velocity.y*3)/4;
1265 		pptr->velocity.z = (pptr->velocity.z*3)/4;
1266 	}
1267 }
1268 
1269 // --------------------------------------------------------------------------------------------------------------------
move_away_from_player(object * objp,vms_vector * vec_to_player,int attack_type)1270 void move_away_from_player(object *objp, vms_vector *vec_to_player, int attack_type)
1271 {
1272 	fix				speed;
1273 	physics_info	*pptr = &objp->mtype.phys_info;
1274 	robot_info		*robptr = &Robot_info[objp->id];
1275 	int				objref;
1276 
1277 	pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16);
1278 	pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16);
1279 	pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16);
1280 
1281 	if (attack_type) {
1282 		//	Get value in 0..3 to choose evasion direction.
1283 		objref = ((objp-Objects) ^ ((FrameCount + 3*(objp-Objects)) >> 5)) & 3;
1284 
1285 		switch (objref) {
1286 			case 0:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, FrameTime << 5);	break;
1287 			case 1:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, -FrameTime << 5);	break;
1288 			case 2:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, FrameTime << 5);	break;
1289 			case 3:	vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, -FrameTime << 5);	break;
1290 			default:	Int3();	//	Impossible, bogus value on objref, must be in 0..3
1291 		}
1292 	}
1293 
1294 
1295 	speed = vm_vec_mag_quick(&pptr->velocity);
1296 
1297 	if (speed > robptr->max_speed[Difficulty_level]) {
1298 		pptr->velocity.x = (pptr->velocity.x*3)/4;
1299 		pptr->velocity.y = (pptr->velocity.y*3)/4;
1300 		pptr->velocity.z = (pptr->velocity.z*3)/4;
1301 	}
1302 
1303 }
1304 
1305 // --------------------------------------------------------------------------------------------------------------------
1306 //	Move towards, away_from or around player.
1307 //	Also deals with evasion.
1308 //	If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
ai_move_relative_to_player(object * objp,ai_local * ailp,fix dist_to_player,vms_vector * vec_to_player,fix circle_distance,int evade_only,int player_visibility)1309 void ai_move_relative_to_player(object *objp, ai_local *ailp, fix dist_to_player, vms_vector *vec_to_player, fix circle_distance, int evade_only, int player_visibility)
1310 {
1311 	object		*dobjp;
1312 	robot_info	*robptr = &Robot_info[objp->id];
1313 
1314 	Assert(player_visibility != -1);
1315 
1316 	//	See if should take avoidance.
1317 
1318 	// New way, green guys don't evade:	if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) {
1319 	if (objp->ctype.ai_info.danger_laser_num != -1) {
1320 		dobjp = &Objects[objp->ctype.ai_info.danger_laser_num];
1321 
1322 		if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
1323 			fix			dot, dist_to_laser, field_of_view;
1324 			vms_vector	vec_to_laser, laser_fvec;
1325 
1326 			field_of_view = Robot_info[objp->id].field_of_view[Difficulty_level];
1327 
1328 			vm_vec_sub(&vec_to_laser, &dobjp->pos, &objp->pos);
1329 			dist_to_laser = vm_vec_normalize_quick(&vec_to_laser);
1330 			dot = vm_vec_dot(&vec_to_laser, &objp->orient.fvec);
1331 
1332 			if ((dot > field_of_view) || (robptr->companion)) {
1333 				fix			laser_robot_dot;
1334 				vms_vector	laser_vec_to_robot;
1335 
1336 				//	The laser is seen by the robot, see if it might hit the robot.
1337 				//	Get the laser's direction.  If it's a polyobj, it can be gotten cheaply from the orientation matrix.
1338 				if (dobjp->render_type == RT_POLYOBJ)
1339 					laser_fvec = dobjp->orient.fvec;
1340 				else {		//	Not a polyobj, get velocity and normalize.
1341 					laser_fvec = dobjp->mtype.phys_info.velocity;	//dobjp->orient.fvec;
1342 					vm_vec_normalize_quick(&laser_fvec);
1343 				}
1344 				vm_vec_sub(&laser_vec_to_robot, &objp->pos, &dobjp->pos);
1345 				vm_vec_normalize_quick(&laser_vec_to_robot);
1346 				laser_robot_dot = vm_vec_dot(&laser_fvec, &laser_vec_to_robot);
1347 
1348 				if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
1349 					int	evade_speed;
1350 
1351 					ai_evaded = 1;
1352 					evade_speed = Robot_info[objp->id].evade_speed[Difficulty_level];
1353 
1354 					move_around_player(objp, vec_to_player, evade_speed);
1355 				}
1356 			}
1357 			return;
1358 		}
1359 	}
1360 
1361 	//	If only allowed to do evade code, then done.
1362 	//	Hmm, perhaps brilliant insight.  If want claw-type guys to keep coming, don't return here after evasion.
1363 	if ((!robptr->attack_type) && (!robptr->thief) && evade_only)
1364 		return;
1365 
1366 	//	If we fall out of above, then no object to be avoided.
1367 	objp->ctype.ai_info.danger_laser_num = -1;
1368 
1369 	//	Green guy selects move around/towards/away based on firing time, not distance.
1370 	if (robptr->attack_type == 1) {
1371 		if (((ailp->next_fire > robptr->firing_wait[Difficulty_level]/4) && (dist_to_player < F1_0*30)) || Player_is_dead) {
1372 			//	1/4 of time, move around player, 3/4 of time, move away from player
1373 			if (d_rand() < 8192) {
1374 				move_around_player(objp, vec_to_player, -1);
1375 			} else {
1376 				move_away_from_player(objp, vec_to_player, 1);
1377 			}
1378 		} else {
1379 			move_towards_player(objp, vec_to_player);
1380 		}
1381 	} else if (robptr->thief) {
1382 		move_towards_player(objp, vec_to_player);
1383 	} else {
1384 		int	objval = ((objp-Objects) & 0x0f) ^ 0x0a;
1385 
1386 		//	Changes here by MK, 12/29/95.  Trying to get rid of endless circling around bots in a large room.
1387 		if (robptr->kamikaze) {
1388 			move_towards_player(objp, vec_to_player);
1389 		} else if (dist_to_player < circle_distance)
1390 			move_away_from_player(objp, vec_to_player, 0);
1391 		else if ((dist_to_player < (3+objval)*circle_distance/2) && (ailp->next_fire > -F1_0)) {
1392 			move_around_player(objp, vec_to_player, -1);
1393 		} else {
1394 			if ((-ailp->next_fire > F1_0 + (objval << 12)) && player_visibility) {
1395 				//	Usually move away, but sometimes move around player.
1396 				if ((((GameTime >> 18) & 0x0f) ^ objval) > 4) {
1397 					move_away_from_player(objp, vec_to_player, 0);
1398 				} else {
1399 					move_around_player(objp, vec_to_player, -1);
1400 				}
1401 			} else
1402 				move_towards_player(objp, vec_to_player);
1403 		}
1404 	}
1405 
1406 }
1407 
1408 // --------------------------------------------------------------------------------------------------------------------
1409 //	Compute a somewhat random, normalized vector.
make_random_vector(vms_vector * vec)1410 void make_random_vector(vms_vector *vec)
1411 {
1412 	vec->x = (d_rand() - 16384) | 1;	// make sure we don't create null vector
1413 	vec->y = d_rand() - 16384;
1414 	vec->z = d_rand() - 16384;
1415 
1416 	vm_vec_normalize_quick(vec);
1417 }
1418 
1419 #ifndef NDEBUG
mprintf_animation_info(object * objp)1420 void mprintf_animation_info(object *objp)
1421 {
1422 	ai_static	*aip = &objp->ctype.ai_info;
1423 	ai_local		*ailp = &Ai_local_info[objp-Objects];
1424 
1425 	if (!Ai_info_enabled)
1426 		return;
1427 
1428 	mprintf((0, "Goal = "));
1429 
1430 	switch (aip->GOAL_STATE) {
1431 		case AIS_NONE:	mprintf((0, "NONE "));	break;
1432 		case AIS_REST:	mprintf((0, "REST "));	break;
1433 		case AIS_SRCH:	mprintf((0, "SRCH "));	break;
1434 		case AIS_LOCK:	mprintf((0, "LOCK "));	break;
1435 		case AIS_FLIN:	mprintf((0, "FLIN "));	break;
1436 		case AIS_FIRE:	mprintf((0, "FIRE "));	break;
1437 		case AIS_RECO:	mprintf((0, "RECO "));	break;
1438 		case AIS_ERR_:	mprintf((0, "ERR_ "));	break;
1439 
1440 	}
1441 
1442 	mprintf((0, " Cur = "));
1443 
1444 	switch (aip->CURRENT_STATE) {
1445 		case AIS_NONE:	mprintf((0, "NONE "));	break;
1446 		case AIS_REST:	mprintf((0, "REST "));	break;
1447 		case AIS_SRCH:	mprintf((0, "SRCH "));	break;
1448 		case AIS_LOCK:	mprintf((0, "LOCK "));	break;
1449 		case AIS_FLIN:	mprintf((0, "FLIN "));	break;
1450 		case AIS_FIRE:	mprintf((0, "FIRE "));	break;
1451 		case AIS_RECO:	mprintf((0, "RECO "));	break;
1452 		case AIS_ERR_:	mprintf((0, "ERR_ "));	break;
1453 	}
1454 
1455 	mprintf((0, " Aware = "));
1456 
1457 	switch (ailp->player_awareness_type) {
1458 		case AIE_FIRE: mprintf((0, "FIRE ")); break;
1459 		case AIE_HITT: mprintf((0, "HITT ")); break;
1460 		case AIE_COLL: mprintf((0, "COLL ")); break;
1461 		case AIE_HURT: mprintf((0, "HURT ")); break;
1462 	}
1463 
1464 	mprintf((0, "Next fire = %6.3f, Time = %6.3f\n", f2fl(ailp->next_fire), f2fl(ailp->player_awareness_time)));
1465 
1466 }
1467 #endif
1468 
1469 //	-------------------------------------------------------------------------------------------------------------------
1470 int	Break_on_object = -1;
1471 
do_firing_stuff(object * obj,int player_visibility,vms_vector * vec_to_player)1472 void do_firing_stuff(object *obj, int player_visibility, vms_vector *vec_to_player)
1473 {
1474 	if ((Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) || (player_visibility >= 1)) {
1475 		//	Now, if in robot's field of view, lock onto player
1476 		fix	dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
1477 		if ((dot >= 7*F1_0/8) || (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) {
1478 			ai_static	*aip = &obj->ctype.ai_info;
1479 			ai_local		*ailp = &Ai_local_info[obj-Objects];
1480 
1481 			switch (aip->GOAL_STATE) {
1482 				case AIS_NONE:
1483 				case AIS_REST:
1484 				case AIS_SRCH:
1485 				case AIS_LOCK:
1486 					aip->GOAL_STATE = AIS_FIRE;
1487 					if (ailp->player_awareness_type <= PA_NEARBY_ROBOT_FIRED) {
1488 						ailp->player_awareness_type = PA_NEARBY_ROBOT_FIRED;
1489 						ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1490 					}
1491 					break;
1492 			}
1493 		} else if (dot >= F1_0/2) {
1494 			ai_static	*aip = &obj->ctype.ai_info;
1495 			switch (aip->GOAL_STATE) {
1496 				case AIS_NONE:
1497 				case AIS_REST:
1498 				case AIS_SRCH:
1499 					aip->GOAL_STATE = AIS_LOCK;
1500 					break;
1501 			}
1502 		}
1503 	}
1504 }
1505 
1506 // --------------------------------------------------------------------------------------------------------------------
1507 //	If a hiding robot gets bumped or hit, he decides to find another hiding place.
do_ai_robot_hit(object * objp,int type)1508 void do_ai_robot_hit(object *objp, int type)
1509 {
1510 	if (objp->control_type == CT_AI) {
1511 		if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_PLAYER_COLLISION))
1512 			switch (objp->ctype.ai_info.behavior) {
1513 				case AIB_STILL:
1514 				{
1515 					int	r;
1516 
1517 					//	Attack robots (eg, green guy) shouldn't have behavior = still.
1518 					Assert(Robot_info[objp->id].attack_type == 0);
1519 
1520 					r = d_rand();
1521 					//	1/8 time, charge player, 1/4 time create path, rest of time, do nothing
1522 					if (r < 4096) {
1523 						// -- mprintf((0, "Still guy switching to Station, creating path to player."));
1524 						create_path_to_player(objp, 10, 1);
1525 						objp->ctype.ai_info.behavior = AIB_STATION;
1526 						objp->ctype.ai_info.hide_segment = objp->segnum;
1527 						Ai_local_info[objp-Objects].mode = AIM_CHASE_OBJECT;
1528 					} else if (r < 4096+8192) {
1529 						// -- mprintf((0, "Still guy creating n segment path."));
1530 						create_n_segment_path(objp, d_rand()/8192 + 2, -1);
1531 						Ai_local_info[objp-Objects].mode = AIM_FOLLOW_PATH;
1532 					}
1533 					break;
1534 				}
1535 			}
1536 	}
1537 
1538 }
1539 #ifndef NDEBUG
1540 int	Do_ai_flag=1;
1541 int	Cvv_test=0;
1542 int	Cvv_last_time[MAX_OBJECTS];
1543 int	Gun_point_hack=0;
1544 #endif
1545 
1546 int		Robot_sound_volume=DEFAULT_ROBOT_SOUND_VOLUME;
1547 
1548 // --------------------------------------------------------------------------------------------------------------------
1549 //	Note: This function could be optimized.  Surely player_is_visible_from_object would benefit from the
1550 //	information of a normalized vec_to_player.
1551 //	Return player visibility:
1552 //		0		not visible
1553 //		1		visible, but robot not looking at player (ie, on an unobstructed vector)
1554 //		2		visible and in robot's field of view
1555 //		-1		player is cloaked
1556 //	If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
1557 //	Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
1558 //	and is copied to player_visibility
compute_vis_and_vec(object * objp,vms_vector * pos,ai_local * ailp,vms_vector * vec_to_player,int * player_visibility,robot_info * robptr,int * flag)1559 void compute_vis_and_vec(object *objp, vms_vector *pos, ai_local *ailp, vms_vector *vec_to_player, int *player_visibility, robot_info *robptr, int *flag)
1560 {
1561 	if (!*flag) {
1562 		if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
1563 			fix			delta_time, dist;
1564 			int			cloak_index = (objp-Objects) % MAX_AI_CLOAK_INFO;
1565 
1566 			delta_time = GameTime - Ai_cloak_info[cloak_index].last_time;
1567 			if (delta_time > F1_0*2) {
1568 				vms_vector	randvec;
1569 
1570 				Ai_cloak_info[cloak_index].last_time = GameTime;
1571 				make_random_vector(&randvec);
1572 				vm_vec_scale_add2(&Ai_cloak_info[cloak_index].last_position, &randvec, 8*delta_time );
1573 			}
1574 
1575 			dist = vm_vec_normalized_dir_quick(vec_to_player, &Ai_cloak_info[cloak_index].last_position, pos);
1576 			*player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1577 			// *player_visibility = 2;
1578 
1579 			if ((ailp->next_misc_sound_time < GameTime) && ((ailp->next_fire < F1_0) || (ailp->next_fire2 < F1_0)) && (dist < F1_0*20)) {
1580 				// mprintf((0, "ANGRY! "));
1581 				ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 1;
1582 				digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1583 			}
1584 		} else {
1585 			//	Compute expensive stuff -- vec_to_player and player_visibility
1586 			vm_vec_normalized_dir_quick(vec_to_player, &Believed_player_pos, pos);
1587 			if ((vec_to_player->x == 0) && (vec_to_player->y == 0) && (vec_to_player->z == 0)) {
1588 				// -- mprintf((0, "Warning: Player and robot at exactly the same location.\n"));
1589 				vec_to_player->x = F1_0;
1590 			}
1591 			*player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1592 
1593 			//	This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
1594 			//	see you without killing frame rate.
1595 			{
1596 				ai_static	*aip = &objp->ctype.ai_info;
1597 			if ((*player_visibility == 2) && (ailp->previous_visibility != 2))
1598 				if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
1599 					aip->GOAL_STATE = AIS_FIRE;
1600 					aip->CURRENT_STATE = AIS_FIRE;
1601 				}
1602 			}
1603 
1604 			if ((ailp->previous_visibility != *player_visibility) && (*player_visibility == 2)) {
1605 				if (ailp->previous_visibility == 0) {
1606 					if (ailp->time_player_seen + F1_0/2 < GameTime) {
1607 						// -- mprintf((0, "SEE! "));
1608 						// -- if (Player_exploded)
1609 						// -- 	digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1610 						// -- else
1611 							digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1612 						ailp->time_player_sound_attacked = GameTime;
1613 						ailp->next_misc_sound_time = GameTime + F1_0 + d_rand()*4;
1614 					}
1615 				} else if (ailp->time_player_sound_attacked + F1_0/4 < GameTime) {
1616 					// -- mprintf((0, "ANGRY! "));
1617 					// -- if (Player_exploded)
1618 					// -- 	digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1619 					// -- else
1620 						digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1621 					ailp->time_player_sound_attacked = GameTime;
1622 				}
1623 			}
1624 
1625 			if ((*player_visibility == 2) && (ailp->next_misc_sound_time < GameTime)) {
1626 				// -- mprintf((0, "ATTACK! "));
1627 				ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 2;
1628 				// -- if (Player_exploded)
1629 				// -- 	digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1630 				// -- else
1631 					digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1632 			}
1633 			ailp->previous_visibility = *player_visibility;
1634 		}
1635 
1636 		*flag = 1;
1637 
1638 		//	@mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
1639 		//	act is if robot is looking at player.
1640 		if (ailp->player_awareness_type >= PA_NEARBY_ROBOT_FIRED)
1641 			if (*player_visibility == 1)
1642 				*player_visibility = 2;
1643 
1644 		if (*player_visibility) {
1645 			ailp->time_player_seen = GameTime;
1646 		}
1647 	}
1648 
1649 }
1650 
1651 // --------------------------------------------------------------------------------------------------------------------
1652 //	Move the object objp to a spot in which it doesn't intersect a wall.
1653 //	It might mean moving it outside its current segment.
move_object_to_legal_spot(object * objp)1654 void move_object_to_legal_spot(object *objp)
1655 {
1656 	vms_vector	original_pos = objp->pos;
1657 	int		i;
1658 	segment	*segp = &Segments[objp->segnum];
1659 
1660 	for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1661 		if (WALL_IS_DOORWAY(segp, i) & WID_FLY_FLAG) {
1662 			vms_vector	segment_center, goal_dir;
1663 			fix			dist_to_center;	// Value not used so far.
1664 
1665 			compute_segment_center(&segment_center, &Segments[segp->children[i]]);
1666 			vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1667 			dist_to_center = vm_vec_normalize_quick(&goal_dir);
1668 			vm_vec_scale(&goal_dir, objp->size);
1669 			vm_vec_add2(&objp->pos, &goal_dir);
1670 			if (!object_intersects_wall(objp)) {
1671 				int	new_segnum = find_point_seg(&objp->pos, objp->segnum);
1672 
1673 				if (new_segnum != -1) {
1674 					obj_relink(objp-Objects, new_segnum);
1675 					return;
1676 				}
1677 			} else
1678 				objp->pos = original_pos;
1679 		}
1680 	}
1681 
1682 	if (Robot_info[objp->id].boss_flag) {
1683 		Int3();		//	Note: Boss is poking outside mine.  Will try to resolve.
1684 		teleport_boss(objp);
1685 	} else {
1686 		mprintf((0, "Note: Killing robot #%i because he's badly stuck outside the mine.\n", objp-Objects));
1687 		apply_damage_to_robot(objp, objp->shields*2, objp-Objects);
1688 	}
1689 }
1690 
1691 // --------------------------------------------------------------------------------------------------------------------
1692 //	Move object one object radii from current position towards segment center.
1693 //	If segment center is nearer than 2 radii, move it to center.
move_towards_segment_center(object * objp)1694 void move_towards_segment_center(object *objp)
1695 {
1696 	int			segnum = objp->segnum;
1697 	fix			dist_to_center;
1698 	vms_vector	segment_center, goal_dir;
1699 
1700 	compute_segment_center(&segment_center, &Segments[segnum]);
1701 
1702 	vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1703 	dist_to_center = vm_vec_normalize_quick(&goal_dir);
1704 
1705 	if (dist_to_center < objp->size) {
1706 		//	Center is nearer than the distance we want to move, so move to center.
1707 		objp->pos = segment_center;
1708 		mprintf((0, "Object #%i moved to center of segment #%i (%7.3f %7.3f %7.3f)\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z)));
1709 		if (object_intersects_wall(objp)) {
1710 			mprintf((0, "Object #%i still illegal, trying trickier move.\n"));
1711 			move_object_to_legal_spot(objp);
1712 		}
1713 	} else {
1714 		int	new_segnum;
1715 		//	Move one radii towards center.
1716 		vm_vec_scale(&goal_dir, objp->size);
1717 		vm_vec_add2(&objp->pos, &goal_dir);
1718 		new_segnum = find_point_seg(&objp->pos, objp->segnum);
1719 		if (new_segnum == -1) {
1720 			objp->pos = segment_center;
1721 			move_object_to_legal_spot(objp);
1722 		}
1723 		// -- mprintf((0, "Obj %i moved twrds seg %i (%6.2f %6.2f %6.2f), dists: [%6.2f %6.2f]\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center)), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center))));
1724 	}
1725 
1726 }
1727 
1728 extern	int	Buddy_objnum;
1729 
1730 //int	Buddy_got_stuck = 0;
1731 
1732 //	-----------------------------------------------------------------------------------------------------------
1733 //	Return true if door can be flown through by a suitable type robot.
1734 //	Brains, avoid robots, companions can open doors.
1735 //	objp == NULL means treat as buddy.
ai_door_is_openable(object * objp,segment * segp,int sidenum)1736 int ai_door_is_openable(object *objp, segment *segp, int sidenum)
1737 {
1738 	int	wall_num;
1739 	wall	*wallp;
1740 
1741 	if (!IS_CHILD(segp->children[sidenum]))
1742 		return 0;		//trap -2 (exit side)
1743 
1744 	wall_num = segp->sides[sidenum].wall_num;
1745 
1746 	if (wall_num == -1)		//if there's no door at all...
1747 		return 0;				//..then say it can't be opened
1748 
1749 	//	The mighty console object can open all doors (for purposes of determining paths).
1750 	if (objp == ConsoleObject) {
1751 
1752 		if (Walls[wall_num].type == WALL_DOOR)
1753 			return 1;
1754 	}
1755 
1756 	wallp = &Walls[wall_num];
1757 
1758 	if ((objp == NULL) || (Robot_info[objp->id].companion == 1)) {
1759 		int	ailp_mode;
1760 
1761 		if (wallp->flags & WALL_BUDDY_PROOF) {
1762 			if ((wallp->type == WALL_DOOR) && (wallp->state == WALL_DOOR_CLOSED))
1763 				return 0;
1764 			else if (wallp->type == WALL_CLOSED)
1765 				return 0;
1766 			else if ((wallp->type == WALL_ILLUSION) && !(wallp->flags & WALL_ILLUSION_OFF))
1767 				return 0;
1768 		}
1769 
1770 		if (wallp->keys != KEY_NONE) {
1771 			if (wallp->keys == KEY_BLUE)
1772 				return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
1773 			else if (wallp->keys == KEY_GOLD)
1774 				return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
1775 			else if (wallp->keys == KEY_RED)
1776 				return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
1777 		}
1778 
1779 		if ((wallp->type != WALL_DOOR) && (wallp->type != WALL_CLOSED))
1780 			return 1;
1781 
1782 		//	If Buddy is returning to player, don't let him think he can get through triggered doors.
1783 		//	It's only valid to think that if the player is going to get him through.  But if he's
1784 		//	going to the player, the player is probably on the opposite side.
1785 		if (objp == NULL)
1786 			ailp_mode = Ai_local_info[Buddy_objnum].mode;
1787 		else
1788 			ailp_mode = Ai_local_info[objp-Objects].mode;
1789 
1790 		// -- if (Buddy_got_stuck) {
1791 		if (ailp_mode == AIM_GOTO_PLAYER) {
1792 			if ((wallp->type == WALL_BLASTABLE) && (wallp->state != WALL_BLASTED))
1793 				return 0;
1794 			if (wallp->type == WALL_CLOSED)
1795 				return 0;
1796 			if (wallp->type == WALL_DOOR) {
1797 				if ((wallp->flags & WALL_DOOR_LOCKED) && (wallp->state == WALL_DOOR_CLOSED))
1798 					return 0;
1799 			}
1800 		}
1801 		// -- }
1802 
1803 		if ((ailp_mode != AIM_GOTO_PLAYER) && (wallp->controlling_trigger != -1)) {
1804 			int	clip_num = wallp->clip_num;
1805 
1806 			if (clip_num == -1)
1807 				return 1;
1808 			else if (WallAnims[clip_num].flags & WCF_HIDDEN) {
1809 				if (wallp->state == WALL_DOOR_CLOSED)
1810 					return 0;
1811 				else
1812 					return 1;
1813 			} else
1814 				return 1;
1815 		}
1816 
1817 		if (wallp->type == WALL_DOOR)  {
1818 			if (wallp->type == WALL_BLASTABLE)
1819 				return 1;
1820 			else {
1821 				int	clip_num = wallp->clip_num;
1822 
1823 				if (clip_num == -1)
1824 					return 1;
1825 				//	Buddy allowed to go through secret doors to get to player.
1826 				else if ((ailp_mode != AIM_GOTO_PLAYER) && (WallAnims[clip_num].flags & WCF_HIDDEN)) {
1827 					if (wallp->state == WALL_DOOR_CLOSED)
1828 						return 0;
1829 					else
1830 						return 1;
1831 				} else
1832 					return 1;
1833 			}
1834 		}
1835 	} else if ((objp->id == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == AIB_RUN_FROM) || (objp->ctype.ai_info.behavior == AIB_SNIPE)) {
1836 		if (wall_num != -1)
1837 		{
1838 			if ((wallp->type == WALL_DOOR) && (wallp->keys == KEY_NONE) && !(wallp->flags & WALL_DOOR_LOCKED))
1839 				return 1;
1840 			else if (wallp->keys != KEY_NONE) {	//	Allow bots to open doors to which player has keys.
1841 				if (wallp->keys & Players[Player_num].flags)
1842 					return 1;
1843 			}
1844 		}
1845 	}
1846 	return 0;
1847 }
1848 
1849 //	-----------------------------------------------------------------------------------------------------------
1850 //	Return side of openable door in segment, if any.  If none, return -1.
openable_doors_in_segment(int segnum)1851 int openable_doors_in_segment(int segnum)
1852 {
1853 	int	i;
1854 
1855 	if ((segnum < 0) || (segnum > Highest_segment_index))
1856 		return -1;
1857 
1858 	for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1859 		if (Segments[segnum].sides[i].wall_num != -1) {
1860 			int	wall_num = Segments[segnum].sides[i].wall_num;
1861 			if ((Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys == KEY_NONE) && (Walls[wall_num].state == WALL_DOOR_CLOSED) && !(Walls[wall_num].flags & WALL_DOOR_LOCKED) && !(WallAnims[Walls[wall_num].clip_num].flags & WCF_HIDDEN))
1862 				return i;
1863 		}
1864 	}
1865 
1866 	return -1;
1867 
1868 }
1869 
1870 // -- // --------------------------------------------------------------------------------------------------------------------
1871 // -- //	Return true if a special object (player or control center) is in this segment.
1872 // -- int special_object_in_seg(int segnum)
1873 // -- {
1874 // -- 	int	objnum;
1875 // --
1876 // -- 	objnum = Segments[segnum].objects;
1877 // --
1878 // -- 	while (objnum != -1) {
1879 // -- 		if ((Objects[objnum].type == OBJ_PLAYER) || (Objects[objnum].type == OBJ_CNTRLCEN)) {
1880 // -- 			mprintf((0, "Special object of type %i in segment %i\n", Objects[objnum].type, segnum));
1881 // -- 			return 1;
1882 // -- 		} else
1883 // -- 			objnum = Objects[objnum].next;
1884 // -- 	}
1885 // --
1886 // -- 	return 0;
1887 // -- }
1888 
1889 // -- // --------------------------------------------------------------------------------------------------------------------
1890 // -- //	Randomly select a segment attached to *segp, reachable by flying.
1891 // -- int get_random_child(int segnum)
1892 // -- {
1893 // -- 	int	sidenum;
1894 // -- 	segment	*segp = &Segments[segnum];
1895 // --
1896 // -- 	sidenum = (rand() * 6) >> 15;
1897 // --
1898 // -- 	while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
1899 // -- 		sidenum = (rand() * 6) >> 15;
1900 // --
1901 // -- 	segnum = segp->children[sidenum];
1902 // --
1903 // -- 	return segnum;
1904 // -- }
1905 
1906 // --------------------------------------------------------------------------------------------------------------------
1907 //	Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
check_object_object_intersection(vms_vector * pos,fix size,segment * segp)1908 int check_object_object_intersection(vms_vector *pos, fix size, segment *segp)
1909 {
1910 	int		curobjnum;
1911 
1912 	//	If this would intersect with another object (only check those in this segment), then try to move.
1913 	curobjnum = segp->objects;
1914 	while (curobjnum != -1) {
1915 		object *curobjp = &Objects[curobjnum];
1916 		if ((curobjp->type == OBJ_PLAYER) || (curobjp->type == OBJ_ROBOT) || (curobjp->type == OBJ_CNTRLCEN)) {
1917 			if (vm_vec_dist_quick(pos, &curobjp->pos) < size + curobjp->size)
1918 				return 1;
1919 		}
1920 		curobjnum = curobjp->next;
1921 	}
1922 
1923 	return 0;
1924 
1925 }
1926 
1927 // --------------------------------------------------------------------------------------------------------------------
1928 //	Return objnum if object created, else return -1.
1929 //	If pos == NULL, pick random spot in segment.
create_gated_robot(int segnum,int object_id,vms_vector * pos)1930 int create_gated_robot( int segnum, int object_id, vms_vector *pos)
1931 {
1932 	int		objnum;
1933 	object	*objp;
1934 	segment	*segp = &Segments[segnum];
1935 	vms_vector	object_pos;
1936 	robot_info	*robptr = &Robot_info[object_id];
1937 	int		i, count=0;
1938 	fix		objsize = Polygon_models[robptr->model_num].rad;
1939 	int		default_behavior;
1940 
1941 	if (GameTime - Last_gate_time < Gate_interval)
1942 		return -1;
1943 
1944 	for (i=0; i<=Highest_object_index; i++)
1945 		if (Objects[i].type == OBJ_ROBOT)
1946 			if (Objects[i].matcen_creator == BOSS_GATE_MATCEN_NUM)
1947 				count++;
1948 
1949 	if (count > 2*Difficulty_level + 6) {
1950 		//mprintf((0, "Cannot gate in a robot until you kill one.\n"));
1951 		Last_gate_time = GameTime - 3*Gate_interval/4;
1952 		return -1;
1953 	}
1954 
1955 	compute_segment_center(&object_pos, segp);
1956 	if (pos == NULL)
1957 		pick_random_point_in_seg(&object_pos, segp-Segments);
1958 	else
1959 		object_pos = *pos;
1960 
1961 	//	See if legal to place object here.  If not, move about in segment and try again.
1962 	if (check_object_object_intersection(&object_pos, objsize, segp)) {
1963 		//mprintf((0, "Can't get in because object collides with something.\n"));
1964 		Last_gate_time = GameTime - 3*Gate_interval/4;
1965 		return -1;
1966 	}
1967 
1968 	objnum = obj_create(OBJ_ROBOT, object_id, segnum, &object_pos, &vmd_identity_matrix, objsize, CT_AI, MT_PHYSICS, RT_POLYOBJ);
1969 
1970 	if ( objnum < 0 ) {
1971 		// mprintf((1, "Can't get object to gate in robot.  Not gating in.\n"));
1972 		Last_gate_time = GameTime - 3*Gate_interval/4;
1973 		return -1;
1974 	}
1975 
1976 	//mprintf((0, "Gating in object %i in segment %i\n", objnum, segp-Segments));
1977 
1978 	Objects[objnum].lifeleft = F1_0*30;	//	Gated in robots only live 30 seconds.
1979 
1980 #ifdef NETWORK
1981 	Net_create_objnums[0] = objnum; // A convenient global to get objnum back to caller for multiplayer
1982 #endif
1983 
1984 	objp = &Objects[objnum];
1985 
1986 	//Set polygon-object-specific data
1987 
1988 	objp->rtype.pobj_info.model_num = robptr->model_num;
1989 	objp->rtype.pobj_info.subobj_flags = 0;
1990 
1991 	//set Physics info
1992 
1993 	objp->mtype.phys_info.mass = robptr->mass;
1994 	objp->mtype.phys_info.drag = robptr->drag;
1995 
1996 	objp->mtype.phys_info.flags |= (PF_LEVELLING);
1997 
1998 	objp->shields = robptr->strength;
1999 	objp->matcen_creator = BOSS_GATE_MATCEN_NUM;	//	flag this robot as having been created by the boss.
2000 
2001 	default_behavior = Robot_info[objp->id].behavior;
2002 	init_ai_object(objp-Objects, default_behavior, -1 );		//	Note, -1 = segment this robot goes to to hide, should probably be something useful
2003 
2004 	object_create_explosion(segnum, &object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
2005 	digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segnum, 0, &object_pos, 0 , F1_0);
2006 	morph_start(objp);
2007 
2008 	Last_gate_time = GameTime;
2009 
2010 	Players[Player_num].num_robots_level++;
2011 	Players[Player_num].num_robots_total++;
2012 
2013 	return objp-Objects;
2014 }
2015 
2016 #define	MAX_SPEW_BOT		3
2017 
2018 int	Spew_bots[NUM_D2_BOSSES][MAX_SPEW_BOT] = {
2019 	{38, 40, -1},
2020 	{37, -1, -1},
2021 	{43, 57, -1},
2022 	{26, 27, 58},
2023 	{59, 58, 54},
2024 	{60, 61, 54},
2025 
2026 	{69, 29, 24},
2027 	{72, 60, 73}
2028 };
2029 
2030 int	Max_spew_bots[NUM_D2_BOSSES] = {2, 1, 2, 3, 3, 3,  3, 3};
2031 
2032 //	----------------------------------------------------------------------------------------------------------
2033 //	objp points at a boss.  He was presumably just hit and he's supposed to create a bot at the hit location *pos.
boss_spew_robot(object * objp,vms_vector * pos)2034 int boss_spew_robot(object *objp, vms_vector *pos)
2035 {
2036 	int		objnum, segnum;
2037 	int		boss_index;
2038 
2039 	boss_index = Robot_info[objp->id].boss_flag - BOSS_D2;
2040 
2041 	Assert((boss_index >= 0) && (boss_index < NUM_D2_BOSSES));
2042 
2043 	segnum = find_point_seg(pos, objp->segnum);
2044 	if (segnum == -1) {
2045 		mprintf((0, "Tried to spew a bot outside the mine!  Aborting!\n"));
2046 		return -1;
2047 	}
2048 
2049 	objnum = create_gated_robot( segnum, Spew_bots[boss_index][(Max_spew_bots[boss_index] * d_rand()) >> 15], pos);
2050 
2051 	//	Make spewed robot come tumbling out as if blasted by a flash missile.
2052 	if (objnum != -1) {
2053 		object	*newobjp = &Objects[objnum];
2054 		int		force_val;
2055 
2056 		force_val = F1_0/FrameTime;
2057 
2058 		if (force_val) {
2059 			newobjp->ctype.ai_info.SKIP_AI_COUNT += force_val;
2060 			newobjp->mtype.phys_info.rotthrust.x = ((d_rand() - 16384) * force_val)/16;
2061 			newobjp->mtype.phys_info.rotthrust.y = ((d_rand() - 16384) * force_val)/16;
2062 			newobjp->mtype.phys_info.rotthrust.z = ((d_rand() - 16384) * force_val)/16;
2063 			newobjp->mtype.phys_info.flags |= PF_USES_THRUST;
2064 
2065 			//	Now, give a big initial velocity to get moving away from boss.
2066 			vm_vec_sub(&newobjp->mtype.phys_info.velocity, pos, &objp->pos);
2067 			vm_vec_normalize_quick(&newobjp->mtype.phys_info.velocity);
2068 			vm_vec_scale(&newobjp->mtype.phys_info.velocity, F1_0*128);
2069 		}
2070 	}
2071 
2072 	return objnum;
2073 }
2074 
2075 // --------------------------------------------------------------------------------------------------------------------
2076 //	Call this each time the player starts a new ship.
init_ai_for_ship(void)2077 void init_ai_for_ship(void)
2078 {
2079 	int	i;
2080 
2081 	for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
2082 		Ai_cloak_info[i].last_time = GameTime;
2083 		Ai_cloak_info[i].last_segment = ConsoleObject->segnum;
2084 		Ai_cloak_info[i].last_position = ConsoleObject->pos;
2085 	}
2086 }
2087 
2088 // --------------------------------------------------------------------------------------------------------------------
2089 //	Make object objp gate in a robot.
2090 //	The process of him bringing in a robot takes one second.
2091 //	Then a robot appears somewhere near the player.
2092 //	Return objnum if robot successfully created, else return -1
gate_in_robot(int type,int segnum)2093 int gate_in_robot(int type, int segnum)
2094 {
2095 	if (segnum < 0)
2096 		segnum = Boss_gate_segs[(d_rand() * Num_boss_gate_segs) >> 15];
2097 
2098 	Assert((segnum >= 0) && (segnum <= Highest_segment_index));
2099 
2100 	return create_gated_robot(segnum, type, NULL);
2101 }
2102 
2103 // --------------------------------------------------------------------------------------------------------------------
boss_fits_in_seg(object * boss_objp,int segnum)2104 int boss_fits_in_seg(object *boss_objp, int segnum)
2105 {
2106 	vms_vector	segcenter;
2107 	int			boss_objnum = boss_objp-Objects;
2108 	int			posnum;
2109 
2110 	compute_segment_center(&segcenter, &Segments[segnum]);
2111 
2112 	for (posnum=0; posnum<9; posnum++) {
2113 		if (posnum > 0) {
2114 			vms_vector	vertex_pos;
2115 
2116 			Assert((posnum-1 >= 0) && (posnum-1 < 8));
2117 			vertex_pos = Vertices[Segments[segnum].verts[posnum-1]];
2118 			vm_vec_avg(&boss_objp->pos, &vertex_pos, &segcenter);
2119 		} else
2120 			boss_objp->pos = segcenter;
2121 
2122 		obj_relink(boss_objnum, segnum);
2123 		if (!object_intersects_wall(boss_objp))
2124 			return 1;
2125 	}
2126 
2127 	return 0;
2128 }
2129 
2130 // --------------------------------------------------------------------------------------------------------------------
teleport_boss(object * objp)2131 void teleport_boss(object *objp)
2132 {
2133 	int			rand_segnum, rand_index;
2134 	vms_vector	boss_dir;
2135 	Assert(Num_boss_teleport_segs > 0);
2136 
2137 	//	Pick a random segment from the list of boss-teleportable-to segments.
2138 	rand_index = (d_rand() * Num_boss_teleport_segs) >> 15;
2139 	rand_segnum = Boss_teleport_segs[rand_index];
2140 	Assert((rand_segnum >= 0) && (rand_segnum <= Highest_segment_index));
2141 
2142 	//mprintf((0, "Frame %i: Boss teleporting to segment #%i, pos = {%8x %8x %8x}.\n", FrameCount, rand_segnum, objp->pos.x, objp->pos.y, objp->pos.z));
2143 
2144 #ifdef NETWORK
2145 	if (Game_mode & GM_MULTI)
2146 		multi_send_boss_actions(objp-Objects, 1, rand_segnum, 0);
2147 #endif
2148 
2149 	compute_segment_center(&objp->pos, &Segments[rand_segnum]);
2150 	obj_relink(objp-Objects, rand_segnum);
2151 
2152 	Last_teleport_time = GameTime;
2153 
2154 	//	make boss point right at player
2155 	vm_vec_sub(&boss_dir, &Objects[Players[Player_num].objnum].pos, &objp->pos);
2156 	vm_vector_2_matrix(&objp->orient, &boss_dir, NULL, NULL);
2157 
2158 	digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segnum, 0, &objp->pos, 0 , F1_0);
2159 	digi_kill_sound_linked_to_object( objp-Objects);
2160 	digi_link_sound_to_object2( Robot_info[objp->id].see_sound, objp-Objects, 1, F1_0, F1_0*512 );	//	F1_0*512 means play twice as loud
2161 
2162 	//	After a teleport, boss can fire right away.
2163 	Ai_local_info[objp-Objects].next_fire = 0;
2164 	Ai_local_info[objp-Objects].next_fire2 = 0;
2165 
2166 }
2167 
2168 //	----------------------------------------------------------------------
start_boss_death_sequence(object * objp)2169 void start_boss_death_sequence(object *objp)
2170 {
2171 	if (Robot_info[objp->id].boss_flag) {
2172 		Boss_dying = 1;
2173 		Boss_dying_start_time = GameTime;
2174 	}
2175 
2176 }
2177 
2178 //	----------------------------------------------------------------------
2179 //	General purpose robot-dies-with-death-roll-and-groan code.
2180 //	Return true if object just died.
2181 //	scale: F1_0*4 for boss, much smaller for much smaller guys
do_robot_dying_frame(object * objp,fix start_time,fix roll_duration,byte * dying_sound_playing,int death_sound,fix expl_scale,fix sound_scale)2182 int do_robot_dying_frame(object *objp, fix start_time, fix roll_duration, byte *dying_sound_playing, int death_sound, fix expl_scale, fix sound_scale)
2183 {
2184 	fix	roll_val, temp;
2185 	fix	sound_duration;
2186 
2187 	if (!roll_duration)
2188 		roll_duration = F1_0/4;
2189 
2190 	roll_val = fixdiv(GameTime - start_time, roll_duration);
2191 
2192 	fix_sincos(fixmul(roll_val, roll_val), &temp, &objp->mtype.phys_info.rotvel.x);
2193 	fix_sincos(roll_val, &temp, &objp->mtype.phys_info.rotvel.y);
2194 	fix_sincos(roll_val-F1_0/8, &temp, &objp->mtype.phys_info.rotvel.z);
2195 
2196 	objp->mtype.phys_info.rotvel.x = (GameTime - start_time)/9;
2197 	objp->mtype.phys_info.rotvel.y = (GameTime - start_time)/5;
2198 	objp->mtype.phys_info.rotvel.z = (GameTime - start_time)/7;
2199 
2200 	if (digi_sample_rate)
2201 		sound_duration = fixdiv(GameSounds[digi_xlat_sound(death_sound)].length,digi_sample_rate);
2202 	else
2203 		sound_duration = F1_0;
2204 
2205 	if (start_time + roll_duration - sound_duration < GameTime) {
2206 		if (!*dying_sound_playing) {
2207 			mprintf((0, "Starting death sound!\n"));
2208 			*dying_sound_playing = 1;
2209 			digi_link_sound_to_object2( death_sound, objp-Objects, 0, sound_scale, sound_scale*256 );	//	F1_0*512 means play twice as loud
2210 		} else if (d_rand() < FrameTime*16)
2211 			create_small_fireball_on_object(objp, (F1_0 + d_rand()) * (16 * expl_scale/F1_0)/8, 0);
2212 	} else if (d_rand() < FrameTime*8)
2213 		create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * (16 * expl_scale/F1_0)/8, 1);
2214 
2215 	if (start_time + roll_duration < GameTime)
2216 		return 1;
2217 	else
2218 		return 0;
2219 }
2220 
2221 //	----------------------------------------------------------------------
start_robot_death_sequence(object * objp)2222 void start_robot_death_sequence(object *objp)
2223 {
2224 	objp->ctype.ai_info.dying_start_time = GameTime;
2225 	objp->ctype.ai_info.dying_sound_playing = 0;
2226 	objp->ctype.ai_info.SKIP_AI_COUNT = 0;
2227 
2228 }
2229 
2230 //	----------------------------------------------------------------------
do_boss_dying_frame(object * objp)2231 void do_boss_dying_frame(object *objp)
2232 {
2233 	int	rval;
2234 
2235 	rval = do_robot_dying_frame(objp, Boss_dying_start_time, BOSS_DEATH_DURATION, &Boss_dying_sound_playing, Robot_info[objp->id].deathroll_sound, F1_0*4, F1_0*4);
2236 
2237 	if (rval) {
2238 		do_controlcen_destroyed_stuff(NULL);
2239 		explode_object(objp, F1_0/4);
2240 		digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp-Objects, 0, F2_0, F1_0*512);
2241 	}
2242 }
2243 
2244 extern void recreate_thief(object *objp);
2245 
2246 //	----------------------------------------------------------------------
do_any_robot_dying_frame(object * objp)2247 int do_any_robot_dying_frame(object *objp)
2248 {
2249 	if (objp->ctype.ai_info.dying_start_time) {
2250 		int	rval, death_roll;
2251 
2252 		death_roll = Robot_info[objp->id].death_roll;
2253 		rval = do_robot_dying_frame(objp, objp->ctype.ai_info.dying_start_time, min(death_roll/2+1,6)*F1_0, &objp->ctype.ai_info.dying_sound_playing, Robot_info[objp->id].deathroll_sound, death_roll*F1_0/8, death_roll*F1_0/2);
2254 
2255 		if (rval) {
2256 			explode_object(objp, F1_0/4);
2257 			digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp-Objects, 0, F2_0, F1_0*512);
2258 			if ((Current_level_num < 0) && (Robot_info[objp->id].thief))
2259 				recreate_thief(objp);
2260 		}
2261 
2262 		return 1;
2263 	}
2264 
2265 	return 0;
2266 }
2267 
2268 // --------------------------------------------------------------------------------------------------------------------
2269 //	Called for an AI object if it is fairly aware of the player.
2270 //	awareness_level is in 0..100.  Larger numbers indicate greater awareness (eg, 99 if firing at player).
2271 //	In a given frame, might not get called for an object, or might be called more than once.
2272 //	The fact that this routine is not called for a given object does not mean that object is not interested in the player.
2273 //	Objects are moved by physics, so they can move even if not interested in a player.  However, if their velocity or
2274 //	orientation is changing, this routine will be called.
2275 //	Return value:
2276 //		0	this player IS NOT allowed to move this robot.
2277 //		1	this player IS allowed to move this robot.
ai_multiplayer_awareness(object * objp,int awareness_level)2278 int ai_multiplayer_awareness(object *objp, int awareness_level)
2279 {
2280 	int	rval=1;
2281 
2282 #ifdef NETWORK
2283 	if (Game_mode & GM_MULTI) {
2284 		if (awareness_level == 0)
2285 			return 0;
2286 		rval = multi_can_move_robot(objp-Objects, awareness_level);
2287 	}
2288 #endif
2289 
2290 	return rval;
2291 
2292 }
2293 
2294 #ifndef NDEBUG
2295 fix	Prev_boss_shields = -1;
2296 #endif
2297 
2298 // --------------------------------------------------------------------------------------------------------------------
2299 //	Do special stuff for a boss.
do_boss_stuff(object * objp,int player_visibility)2300 void do_boss_stuff(object *objp, int player_visibility)
2301 {
2302 	int	boss_id, boss_index;
2303 
2304 	boss_id = Robot_info[objp->id].boss_flag;
2305 
2306 	Assert((boss_id >= BOSS_D2) && (boss_id < BOSS_D2 + NUM_D2_BOSSES));
2307 
2308 	boss_index = boss_id - BOSS_D2;
2309 
2310 #ifndef NDEBUG
2311 	if (objp->shields != Prev_boss_shields) {
2312 		mprintf((0, "Boss shields = %7.3f, object %i\n", f2fl(objp->shields), objp-Objects));
2313 		Prev_boss_shields = objp->shields;
2314 	}
2315 #endif
2316 
2317 	//	New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played.
2318 	if (Last_teleport_time > GameTime)
2319 		Last_teleport_time = GameTime;
2320 
2321 	if (Last_gate_time > GameTime)
2322 		Last_gate_time = GameTime;
2323 
2324 	//	@mk, 10/13/95:  Reason:
2325 	//		Level 4 boss behind locked door.  But he's allowed to teleport out of there.  So he
2326 	//		teleports out of there right away, and blasts player right after first door.
2327 	if (!player_visibility && (GameTime - Boss_hit_time > F1_0*2))
2328 		return;
2329 
2330 	if (!Boss_dying && Boss_teleports[boss_index]) {
2331 		if (objp->ctype.ai_info.CLOAKED == 1) {
2332 			Boss_hit_time = GameTime;	//	Keep the cloak:teleport process going.
2333 			if ((GameTime - Boss_cloak_start_time > BOSS_CLOAK_DURATION/3) && (Boss_cloak_end_time - GameTime > BOSS_CLOAK_DURATION/3) && (GameTime - Last_teleport_time > Boss_teleport_interval)) {
2334 				if (ai_multiplayer_awareness(objp, 98))
2335 					teleport_boss(objp);
2336 			} else if (GameTime - Boss_hit_time > F1_0*2) {
2337 				Last_teleport_time -= Boss_teleport_interval/4;
2338 			}
2339 
2340 			if (GameTime > Boss_cloak_end_time || GameTime < Boss_cloak_start_time)
2341 				objp->ctype.ai_info.CLOAKED = 0;
2342 		} else if ((GameTime - Boss_cloak_end_time > Boss_cloak_interval) || (GameTime - Boss_cloak_end_time < -Boss_cloak_duration)) {
2343 			if (ai_multiplayer_awareness(objp, 95)) {
2344 				Boss_cloak_start_time = GameTime;
2345 				Boss_cloak_end_time = GameTime+Boss_cloak_duration;
2346 				objp->ctype.ai_info.CLOAKED = 1;
2347 #ifdef NETWORK
2348 				if (Game_mode & GM_MULTI)
2349 					multi_send_boss_actions(objp-Objects, 2, 0, 0);
2350 #endif
2351 			}
2352 		}
2353 	}
2354 
2355 }
2356 
2357 #define	BOSS_TO_PLAYER_GATE_DISTANCE	(F1_0*200)
2358 
2359 // -- Obsolete D1 code -- // --------------------------------------------------------------------------------------------------------------------
2360 // -- Obsolete D1 code -- //	Do special stuff for a boss.
2361 // -- Obsolete D1 code -- void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility)
2362 // -- Obsolete D1 code -- {
2363 // -- Obsolete D1 code -- 	static int eclip_state = 0;
2364 // -- Obsolete D1 code --
2365 // -- Obsolete D1 code -- 	do_boss_stuff(objp, player_visibility);
2366 // -- Obsolete D1 code --
2367 // -- Obsolete D1 code -- 	// Only master player can cause gating to occur.
2368 // -- Obsolete D1 code -- 	if ((Game_mode & GM_MULTI) && !network_i_am_master())
2369 // -- Obsolete D1 code -- 		return;
2370 // -- Obsolete D1 code --
2371 // -- Obsolete D1 code -- 	if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) {
2372 // -- Obsolete D1 code -- 		if (GameTime - Last_gate_time > Gate_interval/2) {
2373 // -- Obsolete D1 code -- 			restart_effect(BOSS_ECLIP_NUM);
2374 // -- Obsolete D1 code -- 			if (eclip_state == 0) {
2375 // -- Obsolete D1 code -- 				multi_send_boss_actions(objp-Objects, 4, 0, 0);
2376 // -- Obsolete D1 code -- 				eclip_state = 1;
2377 // -- Obsolete D1 code -- 			}
2378 // -- Obsolete D1 code -- 		}
2379 // -- Obsolete D1 code -- 		else {
2380 // -- Obsolete D1 code -- 			stop_effect(BOSS_ECLIP_NUM);
2381 // -- Obsolete D1 code -- 			if (eclip_state == 1) {
2382 // -- Obsolete D1 code -- 				multi_send_boss_actions(objp-Objects, 5, 0, 0);
2383 // -- Obsolete D1 code -- 				eclip_state = 0;
2384 // -- Obsolete D1 code -- 			}
2385 // -- Obsolete D1 code -- 		}
2386 // -- Obsolete D1 code --
2387 // -- Obsolete D1 code -- 		if (GameTime - Last_gate_time > Gate_interval)
2388 // -- Obsolete D1 code -- 			if (ai_multiplayer_awareness(objp, 99)) {
2389 // -- Obsolete D1 code -- 				int	rtval;
2390 // -- Obsolete D1 code -- 				int	randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
2391 // -- Obsolete D1 code --
2392 // -- Obsolete D1 code -- 				Assert(randtype < MAX_GATE_INDEX);
2393 // -- Obsolete D1 code -- 				randtype = Super_boss_gate_list[randtype];
2394 // -- Obsolete D1 code -- 				Assert(randtype < N_robot_types);
2395 // -- Obsolete D1 code --
2396 // -- Obsolete D1 code -- 				rtval = gate_in_robot(randtype, -1);
2397 // -- Obsolete D1 code -- 				if ((rtval != -1) && (Game_mode & GM_MULTI))
2398 // -- Obsolete D1 code -- 				{
2399 // -- Obsolete D1 code -- 					multi_send_boss_actions(objp-Objects, 3, randtype, Net_create_objnums[0]);
2400 // -- Obsolete D1 code -- 					map_objnum_local_to_local(Net_create_objnums[0]);
2401 // -- Obsolete D1 code -- 				}
2402 // -- Obsolete D1 code -- 			}
2403 // -- Obsolete D1 code -- 	}
2404 // -- Obsolete D1 code -- }
2405 
2406 //int multi_can_move_robot(object *objp, int awareness_level)
2407 //{
2408 //	return 0;
2409 //}
2410 
ai_multi_send_robot_position(int objnum,int force)2411 void ai_multi_send_robot_position(int objnum, int force)
2412 {
2413 #ifdef NETWORK
2414 	if (Game_mode & GM_MULTI)
2415 	{
2416 		if (force != -1)
2417 			multi_send_robot_position(objnum, 1);
2418 		else
2419 			multi_send_robot_position(objnum, 0);
2420 	}
2421 #endif
2422 	return;
2423 }
2424 
2425 // --------------------------------------------------------------------------------------------------------------------
2426 //	Returns true if this object should be allowed to fire at the player.
maybe_ai_do_actual_firing_stuff(object * obj,ai_static * aip)2427 int maybe_ai_do_actual_firing_stuff(object *obj, ai_static *aip)
2428 {
2429 #ifdef NETWORK
2430 	if (Game_mode & GM_MULTI)
2431 		if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN))
2432 			if (aip->CURRENT_STATE == AIS_FIRE)
2433 				return 1;
2434 #endif
2435 
2436 	return 0;
2437 }
2438 
2439 vms_vector	Last_fired_upon_player_pos;
2440 
2441 // --------------------------------------------------------------------------------------------------------------------
2442 //	If fire_anyway, fire even if player is not visible.  We're firing near where we believe him to be.  Perhaps he's
2443 //	lurking behind a corner.
ai_do_actual_firing_stuff(object * obj,ai_static * aip,ai_local * ailp,robot_info * robptr,vms_vector * vec_to_player,fix dist_to_player,vms_vector * gun_point,int player_visibility,int object_animates,int gun_num)2444 void ai_do_actual_firing_stuff(object *obj, ai_static *aip, ai_local *ailp, robot_info *robptr, vms_vector *vec_to_player, fix dist_to_player, vms_vector *gun_point, int player_visibility, int object_animates, int gun_num)
2445 {
2446 	fix	dot;
2447 
2448 	if ((player_visibility == 2) || (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD )) {
2449 		vms_vector	fire_pos;
2450 
2451 		fire_pos = Believed_player_pos;
2452 
2453 		//	Hack: If visibility not == 2, we're here because we're firing at a nearby player.
2454 		//	So, fire at Last_fired_upon_player_pos instead of the player position.
2455 		if (!robptr->attack_type && (player_visibility != 2))
2456 			fire_pos = Last_fired_upon_player_pos;
2457 
2458 		//	Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
2459 		//	Above comment corrected.  Date changed from 1994, to 1995.  Should fix some very subtle bugs, as well as not cause me to wonder, in the future, why I was writing AI code for onearm ten months before he existed.
2460 		if (!object_animates || ready_to_fire(robptr, ailp)) {
2461 			dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
2462 			if ((dot >= 7*F1_0/8) || ((dot > F1_0/4) &&  robptr->boss_flag)) {
2463 
2464 				if (gun_num < Robot_info[obj->id].n_guns) {
2465 					if (robptr->attack_type == 1) {
2466 						if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) {		// robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2467 							if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2468 								return;
2469 							do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2470 						} else {
2471 							// mprintf((0, "Green won't fire: Too far: dist = %7.3f, threshold = %7.3f\n", f2fl(dist_to_player), f2fl(obj->size + ConsoleObject->size + F1_0*2)));
2472 							return;
2473 						}
2474 					} else {
2475 						if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2476 							; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2477 						} else {
2478 							if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2479 								return;
2480 							//	New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2481 							if (gun_num != 0) {
2482 								if (ailp->next_fire <= 0) {
2483 									ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2484 									Last_fired_upon_player_pos = fire_pos;
2485 								}
2486 
2487 								if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2488 									calc_gun_point(gun_point, obj, 0);
2489 									ai_fire_laser_at_player(obj, gun_point, 0, &fire_pos);
2490 									Last_fired_upon_player_pos = fire_pos;
2491 								}
2492 
2493 							} else if (ailp->next_fire <= 0) {
2494 								ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2495 								Last_fired_upon_player_pos = fire_pos;
2496 							}
2497 						}
2498 					}
2499 
2500 					//	Wants to fire, so should go into chase mode, probably.
2501 					if ( (aip->behavior != AIB_RUN_FROM)
2502 						 && (aip->behavior != AIB_STILL)
2503 						 && (aip->behavior != AIB_SNIPE)
2504 						 && (aip->behavior != AIB_FOLLOW)
2505 						 && (!robptr->attack_type)
2506 						 && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL)))
2507 						ailp->mode = AIM_CHASE_OBJECT;
2508 				}
2509 
2510 				aip->GOAL_STATE = AIS_RECO;
2511 				ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2512 
2513 				// Switch to next gun for next fire.  If has 2 gun types, select gun #1, if exists.
2514 				aip->CURRENT_GUN++;
2515 				if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2516 				{
2517 					if ((Robot_info[obj->id].n_guns == 1) || (Robot_info[obj->id].weapon_type2 == -1))
2518 						aip->CURRENT_GUN = 0;
2519 					else
2520 						aip->CURRENT_GUN = 1;
2521 				}
2522 			}
2523 		}
2524 	} else if ( ((!robptr->attack_type) && (Weapon_info[Robot_info[obj->id].weapon_type].homing_flag == 1)) || (((Robot_info[obj->id].weapon_type2 != -1) && (Weapon_info[Robot_info[obj->id].weapon_type2].homing_flag == 1))) ) {
2525 		//	Robots which fire homing weapons might fire even if they don't have a bead on the player.
2526 		if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE))
2527 			 && (((ailp->next_fire <= 0) && (aip->CURRENT_GUN != 0)) || ((ailp->next_fire2 <= 0) && (aip->CURRENT_GUN == 0)))
2528 			 && (vm_vec_dist_quick(&Hit_pos, &obj->pos) > F1_0*40)) {
2529 			if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2530 				return;
2531 			ai_fire_laser_at_player(obj, gun_point, gun_num, &Believed_player_pos);
2532 
2533 			aip->GOAL_STATE = AIS_RECO;
2534 			ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2535 
2536 			// Switch to next gun for next fire.
2537 			aip->CURRENT_GUN++;
2538 			if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2539 				aip->CURRENT_GUN = 0;
2540 		} else {
2541 			// Switch to next gun for next fire.
2542 			aip->CURRENT_GUN++;
2543 			if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2544 				aip->CURRENT_GUN = 0;
2545 		}
2546 	} else {
2547 
2548 
2549 	//	---------------------------------------------------------------
2550 
2551 		vms_vector	vec_to_last_pos;
2552 
2553 		if (d_rand()/2 < fixmul(FrameTime, (Difficulty_level << 12) + 0x4000)) {
2554 		if ((!object_animates || ready_to_fire(robptr, ailp)) && (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)) {
2555 			vm_vec_normalized_dir_quick(&vec_to_last_pos, &Believed_player_pos, &obj->pos);
2556 			dot = vm_vec_dot(&obj->orient.fvec, &vec_to_last_pos);
2557 			if (dot >= 7*F1_0/8) {
2558 
2559 				if (aip->CURRENT_GUN < Robot_info[obj->id].n_guns) {
2560 					if (robptr->attack_type == 1) {
2561 						if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) {		// robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2562 							if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2563 								return;
2564 							do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2565 						} else {
2566 							// mprintf((0, "Green won't fire: Too far: dist = %7.3f, threshold = %7.3f\n", f2fl(dist_to_player), f2fl(obj->size + ConsoleObject->size + F1_0*2)));
2567 							return;
2568 						}
2569 					} else {
2570 						if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2571 							; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2572 						} else {
2573 							if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2574 								return;
2575 							//	New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2576 							if (gun_num != 0) {
2577 								if (ailp->next_fire <= 0)
2578 									ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2579 
2580 								if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2581 									calc_gun_point(gun_point, obj, 0);
2582 									ai_fire_laser_at_player(obj, gun_point, 0, &Last_fired_upon_player_pos);
2583 								}
2584 
2585 							} else if (ailp->next_fire <= 0)
2586 								ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2587 						}
2588 					}
2589 
2590 					//	Wants to fire, so should go into chase mode, probably.
2591 					if ( (aip->behavior != AIB_RUN_FROM) && (aip->behavior != AIB_STILL) && (aip->behavior != AIB_SNIPE) && (aip->behavior != AIB_FOLLOW) && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL)))
2592 						ailp->mode = AIM_CHASE_OBJECT;
2593 				}
2594 				aip->GOAL_STATE = AIS_RECO;
2595 				ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2596 
2597 				// Switch to next gun for next fire.
2598 				aip->CURRENT_GUN++;
2599 				if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2600 				{
2601 					if (Robot_info[obj->id].n_guns == 1)
2602 						aip->CURRENT_GUN = 0;
2603 					else
2604 						aip->CURRENT_GUN = 1;
2605 				}
2606 			}
2607 		}
2608 		}
2609 
2610 
2611 	//	---------------------------------------------------------------
2612 
2613 
2614 	}
2615 
2616 }
2617 
2618 
2619