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