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