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