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