added -udp documentation from d1x
[btb/d2x.git] / main / escort.c
1 /* $Id: escort.c,v 1.7 2003-10-10 09:36:35 btb Exp $ */
2 /*
3 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
4 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
5 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
6 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
7 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
8 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
9 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
10 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
11 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
12 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
13 */
14
15 /*
16  *
17  * Escort robot behavior.
18  *
19  * Old Log:
20  * Revision 1.1  1995/05/06  23:32:19  mike
21  * Initial revision
22  *
23  *
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <conf.h>
28 #endif
29
30 #include <stdio.h>              // for printf()
31 #include <stdlib.h>             // for rand() and qsort()
32 #include <string.h>             // for memset()
33
34 #include "inferno.h"
35 #include "mono.h"
36 #include "fix.h"
37 #include "vecmat.h"
38 #include "gr.h"
39 #include "3d.h"
40 #include "palette.h"
41
42 #include "object.h"
43 #include "error.h"
44 #include "ai.h"
45 #include "robot.h"
46 #include "fvi.h"
47 #include "physics.h"
48 #include "wall.h"
49 #include "player.h"
50 #include "fireball.h"
51 #include "game.h"
52 #include "powerup.h"
53 #include "cntrlcen.h"
54 #include "gauges.h"
55 #include "key.h"
56 #include "fuelcen.h"
57 #include "sounds.h"
58 #include "screens.h"
59 #include "text.h"
60 #include "gamefont.h"
61 #include "newmenu.h"
62 #include "playsave.h"
63 #include "gameseq.h"
64 #include "automap.h"
65 #include "laser.h"
66 #include "pa_enabl.h"
67 #include "escort.h"
68
69 #ifdef EDITOR
70 #include "editor/editor.h"
71 #endif
72
73 extern void multi_send_stolen_items();
74 void say_escort_goal(int goal_num);
75 void show_escort_menu(char *msg);
76
77
78 char *Escort_goal_text[MAX_ESCORT_GOALS] = {
79         "BLUE KEY",
80         "YELLOW KEY",
81         "RED KEY",
82         "REACTOR",
83         "EXIT",
84         "ENERGY",
85         "ENERGYCEN",
86         "SHIELD",
87         "POWERUP",
88         "ROBOT",
89         "HOSTAGES",
90         "SPEW",
91         "SCRAM",
92         "EXIT",
93         "BOSS",
94         "MARKER 1",
95         "MARKER 2",
96         "MARKER 3",
97         "MARKER 4",
98         "MARKER 5",
99         "MARKER 6",
100         "MARKER 7",
101         "MARKER 8",
102         "MARKER 9",
103 // -- too much work --  "KAMIKAZE  "
104 };
105
106 int     Max_escort_length = 200;
107 int     Escort_kill_object = -1;
108 ubyte Stolen_items[MAX_STOLEN_ITEMS];
109 int     Stolen_item_index;
110 fix     Escort_last_path_created = 0;
111 int     Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Escort_goal_index = -1, Buddy_messages_suppressed = 0;
112 fix     Buddy_sorry_time;
113 int     Buddy_objnum, Buddy_allowed_to_talk;
114 int     Looking_for_marker;
115 int     Last_buddy_key;
116
117 fix     Last_buddy_message_time;
118
119 char guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT";
120 char real_guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT";
121
122 void init_buddy_for_level(void)
123 {
124         int     i;
125
126         Buddy_allowed_to_talk = 0;
127         Buddy_objnum = -1;
128         Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
129         Escort_special_goal = -1;
130         Escort_goal_index = -1;
131         Buddy_messages_suppressed = 0;
132
133         for (i=0; i<=Highest_object_index; i++)
134                 if (Robot_info[Objects[i].id].companion)
135                         break;
136         if (i <= Highest_object_index)
137                 Buddy_objnum = i;
138
139         Buddy_sorry_time = -F1_0;
140
141         Looking_for_marker = -1;
142         Last_buddy_key = -1;
143 }
144
145 //      -----------------------------------------------------------------------------
146 //      See if segment from curseg through sidenum is reachable.
147 //      Return true if it is reachable, else return false.
148 int segment_is_reachable(int curseg, int sidenum)
149 {
150         int             wall_num, rval;
151         segment *segp = &Segments[curseg];
152
153         if (!IS_CHILD(segp->children[sidenum]))
154                 return 0;
155
156         wall_num = segp->sides[sidenum].wall_num;
157
158         //      If no wall, then it is reachable
159         if (wall_num == -1)
160                 return 1;
161
162         rval = ai_door_is_openable(NULL, segp, sidenum);
163
164         return rval;
165
166 // -- MK, 10/17/95 -- 
167 // -- MK, 10/17/95 --   //      Hmm, a closed wall.  I think this mean not reachable.
168 // -- MK, 10/17/95 --   if (Walls[wall_num].type == WALL_CLOSED)
169 // -- MK, 10/17/95 --           return 0;
170 // -- MK, 10/17/95 -- 
171 // -- MK, 10/17/95 --   if (Walls[wall_num].type == WALL_DOOR) {
172 // -- MK, 10/17/95 --           if (Walls[wall_num].keys == KEY_NONE) {
173 // -- MK, 10/17/95 --                   return 1;               //      @MK, 10/17/95: Be consistent with ai_door_is_openable
174 // -- MK, 10/17/95 -- // --                     if (Walls[wall_num].flags & WALL_DOOR_LOCKED)
175 // -- MK, 10/17/95 -- // --                             return 0;
176 // -- MK, 10/17/95 -- // --                     else
177 // -- MK, 10/17/95 -- // --                             return 1;
178 // -- MK, 10/17/95 --           } else if (Walls[wall_num].keys == KEY_BLUE)
179 // -- MK, 10/17/95 --                   return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
180 // -- MK, 10/17/95 --           else if (Walls[wall_num].keys == KEY_GOLD)
181 // -- MK, 10/17/95 --                   return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
182 // -- MK, 10/17/95 --           else if (Walls[wall_num].keys == KEY_RED)
183 // -- MK, 10/17/95 --                   return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
184 // -- MK, 10/17/95 --           else
185 // -- MK, 10/17/95 --                   Int3(); //      Impossible!  Doesn't have no key, but doesn't have any key!
186 // -- MK, 10/17/95 --   } else
187 // -- MK, 10/17/95 --           return 1;
188 // -- MK, 10/17/95 -- 
189 // -- MK, 10/17/95 --   Int3(); //      Hmm, thought 'if' above had to return!
190 // -- MK, 10/17/95 --   return 0;
191
192 }
193
194
195 //      -----------------------------------------------------------------------------
196 //      Create a breadth-first list of segments reachable from current segment.
197 //      max_segs is maximum number of segments to search.  Use MAX_SEGMENTS to search all.
198 //      On exit, *length <= max_segs.
199 //      Input:
200 //              start_seg
201 //      Output:
202 //              bfs_list:       array of shorts, each reachable segment.  Includes start segment.
203 //              length:         number of elements in bfs_list
204 void create_bfs_list(int start_seg, short bfs_list[], int *length, int max_segs)
205 {
206         int     i, head, tail;
207         sbyte   visited[MAX_SEGMENTS];
208
209         for (i=0; i<MAX_SEGMENTS; i++)
210                 visited[i] = 0;
211
212         head = 0;
213         tail = 0;
214
215         bfs_list[head++] = start_seg;
216         visited[start_seg] = 1;
217
218         while ((head != tail) && (head < max_segs)) {
219                 int             i;
220                 int             curseg;
221                 segment *cursegp;
222
223                 curseg = bfs_list[tail++];
224                 cursegp = &Segments[curseg];
225
226                 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
227                         int     connected_seg;
228
229                         connected_seg = cursegp->children[i];
230
231                         if (IS_CHILD(connected_seg) && (visited[connected_seg] == 0)) {
232                                 if (segment_is_reachable(curseg, i)) {
233                                         bfs_list[head++] = connected_seg;
234                                         if (head >= max_segs)
235                                                 break;
236                                         visited[connected_seg] = 1;
237                                         Assert(head < MAX_SEGMENTS);
238                                 }
239                         }
240                 }
241         }
242
243         *length = head;
244         
245 }
246
247 //      -----------------------------------------------------------------------------
248 //      Return true if ok for buddy to talk, else return false.
249 //      Buddy is allowed to talk if the segment he is in does not contain a blastable wall that has not been blasted
250 //      AND he has never yet, since being initialized for level, been allowed to talk.
251 int ok_for_buddy_to_talk(void)
252 {
253         int             i;
254         segment *segp;
255
256         if (Objects[Buddy_objnum].type != OBJ_ROBOT) {
257                 Buddy_allowed_to_talk = 0;
258                 return 0;
259         }
260
261         if (Buddy_allowed_to_talk)
262                 return 1;
263
264         if ((Objects[Buddy_objnum].type == OBJ_ROBOT) && (Buddy_objnum <= Highest_object_index) && !Robot_info[Objects[Buddy_objnum].id].companion) {
265                 for (i=0; i<=Highest_object_index; i++)
266                         if (Robot_info[Objects[i].id].companion)
267                                 break;
268                 if (i > Highest_object_index)
269                         return 0;
270                 else
271                         Buddy_objnum = i;
272         }
273
274         segp = &Segments[Objects[Buddy_objnum].segnum];
275
276         for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
277                 int     wall_num = segp->sides[i].wall_num;
278
279                 if (wall_num != -1) {
280                         if ((Walls[wall_num].type == WALL_BLASTABLE) && !(Walls[wall_num].flags & WALL_BLASTED))
281                                 return 0;
282                 }
283
284                 //      Check one level deeper.
285                 if (IS_CHILD(segp->children[i])) {
286                         int             j;
287                         segment *csegp = &Segments[segp->children[i]];
288
289                         for (j=0; j<MAX_SIDES_PER_SEGMENT; j++) {
290                                 int     wall2 = csegp->sides[j].wall_num;
291
292                                 if (wall2 != -1) {
293                                         if ((Walls[wall2].type == WALL_BLASTABLE) && !(Walls[wall2].flags & WALL_BLASTED))
294                                                 return 0;
295                                 }
296                         }
297                 }
298         }
299
300         Buddy_allowed_to_talk = 1;
301         return 1;
302 }
303
304 //      --------------------------------------------------------------------------------------------
305 void detect_escort_goal_accomplished(int index)
306 {
307         int     i,j;
308         int     detected = 0;
309
310         if (!Buddy_allowed_to_talk)
311                 return;
312
313         //      If goal is to go away, how can it be achieved?
314         if (Escort_special_goal == ESCORT_GOAL_SCRAM)
315                 return;
316
317 //      See if goal found was a key.  Need to handle default goals differently.
318 //      Note, no buddy_met_goal sound when blow up reactor or exit.  Not great, but ok
319 //      since for reactor, noisy, for exit, buddy is disappearing.
320 if ((Escort_special_goal == -1) && (Escort_goal_index == index)) {
321         detected = 1;
322         goto dega_ok;
323 }
324
325 if ((Escort_goal_index <= ESCORT_GOAL_RED_KEY) && (index >= 0)) {
326         if (Objects[index].type == OBJ_POWERUP)  {
327                 if (Objects[index].id == POW_KEY_BLUE) {
328                         if (Escort_goal_index == ESCORT_GOAL_BLUE_KEY) {
329                                 detected = 1;
330                                 goto dega_ok;
331                         }
332                 } else if (Objects[index].id == POW_KEY_GOLD) {
333                         if (Escort_goal_index == ESCORT_GOAL_GOLD_KEY) {
334                                 detected = 1;
335                                 goto dega_ok;
336                         }
337                 } else if (Objects[index].id == POW_KEY_RED) {
338                         if (Escort_goal_index == ESCORT_GOAL_RED_KEY) {
339                                 detected = 1;
340                                 goto dega_ok;
341                         }
342                 }
343         }
344 }
345         if (Escort_special_goal != -1)
346         {
347                 if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) {
348                         if (index == -4)
349                                 detected = 1;
350                         else {
351                                 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
352                                         if (Segments[index].children[i] == Escort_goal_index) {
353                                                 detected = 1;
354                                                 goto dega_ok;
355                                         } else {
356                                                 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
357                                                         if (Segments[i].children[j] == Escort_goal_index) {
358                                                                 detected = 1;
359                                                                 goto dega_ok;
360                                                         }
361                                         }
362                         }
363                 } else if ((Objects[index].type == OBJ_POWERUP) && (Escort_special_goal == ESCORT_GOAL_POWERUP))
364                         detected = 1;   //      Any type of powerup picked up will do.
365                 else if ((Objects[index].type == Objects[Escort_goal_index].type) && (Objects[index].id == Objects[Escort_goal_index].id)) {
366                         //      Note: This will help a little bit in making the buddy believe a goal is satisfied.  Won't work for a general goal like "find any powerup"
367                         // because of the insistence of both type and id matching.
368                         detected = 1;
369                 }
370         }
371
372 dega_ok: ;
373         if (detected && ok_for_buddy_to_talk()) {
374                 digi_play_sample_once(SOUND_BUDDY_MET_GOAL, F1_0);
375                 Escort_goal_index = -1;
376                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
377                 Escort_special_goal = -1;
378                 Looking_for_marker = -1;
379         }
380
381 }
382
383 void change_guidebot_name()
384 {
385         newmenu_item m;
386         char text[GUIDEBOT_NAME_LEN+1]="";
387         int item;
388
389         strcpy(text,guidebot_name);
390
391         m.type=NM_TYPE_INPUT; m.text_len = GUIDEBOT_NAME_LEN; m.text = text;
392         item = newmenu_do( NULL, "Enter Guide-bot name:", 1, &m, NULL );
393
394         if (item != -1) {
395                 strcpy(guidebot_name,text);
396                 strcpy(real_guidebot_name,text);
397                 write_player_file();
398         }
399 }
400
401 //      -----------------------------------------------------------------------------
402 void buddy_message(char * format, ... )
403 {
404         if (Buddy_messages_suppressed)
405                 return;
406
407         if (Game_mode & GM_MULTI)
408                 return;
409
410         if ((Last_buddy_message_time + F1_0 < GameTime) || (Last_buddy_message_time > GameTime)) {
411                 if (ok_for_buddy_to_talk()) {
412                         char    gb_str[16], new_format[128];
413                         va_list args;
414                         int t;
415
416                         va_start(args, format );
417                         vsprintf(new_format, format, args);
418                         va_end(args);
419
420                         gb_str[0] = 1;
421                         gb_str[1] = BM_XRGB(28, 0, 0);
422                         strcpy(&gb_str[2], guidebot_name);
423                         t = strlen(gb_str);
424                         gb_str[t] = ':';
425                         gb_str[t+1] = 1;
426                         gb_str[t+2] = BM_XRGB(0, 31, 0);
427                         gb_str[t+3] = 0;
428
429                         HUD_init_message("%s %s", gb_str, new_format);
430
431                         Last_buddy_message_time = GameTime;
432                 }
433         }
434
435 }
436
437 //      -----------------------------------------------------------------------------
438 void thief_message(char * format, ... )
439 {
440
441         char    gb_str[16], new_format[128];
442         va_list args;
443
444         va_start(args, format );
445         vsprintf(new_format, format, args);
446         va_end(args);
447
448         gb_str[0] = 1;
449         gb_str[1] = BM_XRGB(28, 0, 0);
450         strcpy(&gb_str[2], "THIEF:");
451         gb_str[8] = 1;
452         gb_str[9] = BM_XRGB(0, 31, 0);
453         gb_str[10] = 0;
454
455         HUD_init_message("%s %s", gb_str, new_format);
456
457 }
458
459 //      -----------------------------------------------------------------------------
460 //      Return true if marker #id has been placed.
461 int marker_exists_in_mine(int id)
462 {
463         int     i;
464
465         for (i=0; i<=Highest_object_index; i++)
466                 if (Objects[i].type == OBJ_MARKER)
467                         if (Objects[i].id == id)
468                                 return 1;
469
470         return 0;
471 }
472
473 //      -----------------------------------------------------------------------------
474 void set_escort_special_goal(int special_key)
475 {
476         int marker_key;
477
478         Buddy_messages_suppressed = 0;
479
480         if (!Buddy_allowed_to_talk) {
481                 ok_for_buddy_to_talk();
482                 if (!Buddy_allowed_to_talk) {
483                         int     i;
484
485                         for (i=0; i<=Highest_object_index; i++)
486                                 if ((Objects[i].type == OBJ_ROBOT) && Robot_info[Objects[i].id].companion) {
487                                         HUD_init_message("%s has not been released.",guidebot_name);
488                                         break;
489                                 }
490                         if (i == Highest_object_index+1)
491                                 HUD_init_message("No Guide-Bot in mine.");
492
493                         return;
494                 }
495         }
496
497         special_key = special_key & (~KEY_SHIFTED);
498
499         marker_key = special_key;
500         
501         #ifdef MACINTOSH
502         switch(special_key) {
503                 case KEY_5:
504                         marker_key = KEY_1+4;
505                         break;
506                 case KEY_6:
507                         marker_key = KEY_1+5;
508                         break;
509                 case KEY_7:
510                         marker_key = KEY_1+6;
511                         break;
512                 case KEY_8:
513                         marker_key = KEY_1+7;
514                         break;
515                 case KEY_9:
516                         marker_key = KEY_1+8;
517                         break;
518                 case KEY_0:
519                         marker_key = KEY_1+9;
520                         break;
521         }
522         #endif
523
524         if (Last_buddy_key == special_key)
525         {
526                 if ((Looking_for_marker == -1) && (special_key != KEY_0)) {
527                         if (marker_exists_in_mine(marker_key - KEY_1))
528                                 Looking_for_marker = marker_key - KEY_1;
529                         else {
530                                 Last_buddy_message_time = 0;    //      Force this message to get through.
531                                 buddy_message("Marker %i not placed.", marker_key - KEY_1 + 1);
532                                 Looking_for_marker = -1;
533                         }
534                 } else {
535                         Looking_for_marker = -1;
536                 }
537         }
538
539         Last_buddy_key = special_key;
540
541         if (special_key == KEY_0)
542                 Looking_for_marker = -1;
543                 
544         if ( Looking_for_marker != -1 ) {
545                 Escort_special_goal = ESCORT_GOAL_MARKER1 + marker_key - KEY_1;
546         } else {
547                 switch (special_key) {
548                         case KEY_1:     Escort_special_goal = ESCORT_GOAL_ENERGY;                       break;
549                         case KEY_2:     Escort_special_goal = ESCORT_GOAL_ENERGYCEN;            break;
550                         case KEY_3:     Escort_special_goal = ESCORT_GOAL_SHIELD;                       break;
551                         case KEY_4:     Escort_special_goal = ESCORT_GOAL_POWERUP;              break;
552                         case KEY_5:     Escort_special_goal = ESCORT_GOAL_ROBOT;                        break;
553                         case KEY_6:     Escort_special_goal = ESCORT_GOAL_HOSTAGE;              break;
554                         case KEY_7:     Escort_special_goal = ESCORT_GOAL_SCRAM;                        break;
555                         case KEY_8:     Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW;  break;
556                         case KEY_9:     Escort_special_goal = ESCORT_GOAL_EXIT;                 break;
557                         case KEY_0:     Escort_special_goal = -1;                                                               break;
558                         default:
559                                 Int3();         //      Oops, called with illegal key value.
560                 }
561         }
562
563         Last_buddy_message_time = GameTime - 2*F1_0;    //      Allow next message to come through.
564
565         say_escort_goal(Escort_special_goal);
566         // -- Escort_goal_object = escort_set_goal_object();
567
568         Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
569 }
570
571 // -- old, pre-bfs, way -- //   -----------------------------------------------------------------------------
572 // -- old, pre-bfs, way -- //   Return object of interest.
573 // -- old, pre-bfs, way -- int exists_in_mine(int objtype, int objid)
574 // -- old, pre-bfs, way -- {
575 // -- old, pre-bfs, way --      int     i;
576 // -- old, pre-bfs, way -- 
577 // -- old, pre-bfs, way --      mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
578 // -- old, pre-bfs, way -- 
579 // -- old, pre-bfs, way --      if (objtype == FUELCEN_CHECK) {
580 // -- old, pre-bfs, way --              for (i=0; i<=Highest_segment_index; i++)
581 // -- old, pre-bfs, way --                      if (Segments[i].special == SEGMENT_IS_FUELCEN)
582 // -- old, pre-bfs, way --                              return i;
583 // -- old, pre-bfs, way --      } else {
584 // -- old, pre-bfs, way --              for (i=0; i<=Highest_object_index; i++) {
585 // -- old, pre-bfs, way --                      if (Objects[i].type == objtype) {
586 // -- old, pre-bfs, way --                              //      Don't find escort robots if looking for robot!
587 // -- old, pre-bfs, way --                              if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].companion))
588 // -- old, pre-bfs, way --                                      continue;
589 // -- old, pre-bfs, way -- 
590 // -- old, pre-bfs, way --                              if (objid == -1) {
591 // -- old, pre-bfs, way --                                      if ((objtype == OBJ_POWERUP) && (Objects[i].id != POW_KEY_BLUE) && (Objects[i].id != POW_KEY_GOLD) && (Objects[i].id != POW_KEY_RED))
592 // -- old, pre-bfs, way --                                              return i;
593 // -- old, pre-bfs, way --                                      else
594 // -- old, pre-bfs, way --                                              return i;
595 // -- old, pre-bfs, way --                              } else if (Objects[i].id == objid)
596 // -- old, pre-bfs, way --                                      return i;
597 // -- old, pre-bfs, way --                      }
598 // -- old, pre-bfs, way --              }
599 // -- old, pre-bfs, way --      }
600 // -- old, pre-bfs, way -- 
601 // -- old, pre-bfs, way --      return -1;
602 // -- old, pre-bfs, way -- 
603 // -- old, pre-bfs, way -- }
604
605 //      -----------------------------------------------------------------------------
606 //      Return id of boss.
607 int get_boss_id(void)
608 {
609         int     i;
610
611         for (i=0; i<=Highest_object_index; i++)
612                 if (Objects[i].type == OBJ_ROBOT)
613                         if (Robot_info[Objects[i].id].boss_flag)
614                                 return Objects[i].id;
615
616         return -1;
617 }
618
619 //      -----------------------------------------------------------------------------
620 //      Return object index if object of objtype, objid exists in mine, else return -1
621 //      "special" is used to find objects spewed by player which is hacked into flags field of powerup.
622 int exists_in_mine_2(int segnum, int objtype, int objid, int special)
623 {
624         if (Segments[segnum].objects != -1) {
625                 int             objnum = Segments[segnum].objects;
626
627                 while (objnum != -1) {
628                         object  *curobjp = &Objects[objnum];
629
630                         if (special == ESCORT_GOAL_PLAYER_SPEW) {
631                                 if (curobjp->flags & OF_PLAYER_DROPPED)
632                                         return objnum;
633                         }
634
635                         if (curobjp->type == objtype) {
636                                 //      Don't find escort robots if looking for robot!
637                                 if ((curobjp->type == OBJ_ROBOT) && (Robot_info[curobjp->id].companion))
638                                         ;
639                                 else if (objid == -1) {
640                                         if ((objtype == OBJ_POWERUP) && (curobjp->id != POW_KEY_BLUE) && (curobjp->id != POW_KEY_GOLD) && (curobjp->id != POW_KEY_RED))
641                                                 return objnum;
642                                         else
643                                                 return objnum;
644                                 } else if (curobjp->id == objid)
645                                         return objnum;
646                         }
647
648                         if (objtype == OBJ_POWERUP)
649                                 if (curobjp->contains_count)
650                                         if (curobjp->contains_type == OBJ_POWERUP)
651                                                 if (curobjp->contains_id == objid)
652                                                         return objnum;
653
654                         objnum = curobjp->next;
655                 }
656         }
657
658         return -1;
659 }
660
661 //      -----------------------------------------------------------------------------
662 //      Return nearest object of interest.
663 //      If special == ESCORT_GOAL_PLAYER_SPEW, then looking for any object spewed by player.
664 //      -1 means object does not exist in mine.
665 //      -2 means object does exist in mine, but buddy-bot can't reach it (eg, behind triggered wall)
666 int exists_in_mine(int start_seg, int objtype, int objid, int special)
667 {
668         int     segindex, segnum;
669         short   bfs_list[MAX_SEGMENTS];
670         int     length;
671
672 //      mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
673
674         create_bfs_list(start_seg, bfs_list, &length, MAX_SEGMENTS);
675
676         if (objtype == FUELCEN_CHECK) {
677                 for (segindex=0; segindex<length; segindex++) {
678                         segnum = bfs_list[segindex];
679                         if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
680                                 return segnum;
681                 }
682         } else {
683                 for (segindex=0; segindex<length; segindex++) {
684                         int     objnum;
685
686                         segnum = bfs_list[segindex];
687
688                         objnum = exists_in_mine_2(segnum, objtype, objid, special);
689                         if (objnum != -1)
690                                 return objnum;
691
692                 }
693         }
694
695         //      Couldn't find what we're looking for by looking at connectivity.
696         //      See if it's in the mine.  It could be hidden behind a trigger or switch
697         //      which the buddybot doesn't understand.
698         if (objtype == FUELCEN_CHECK) {
699                 for (segnum=0; segnum<=Highest_segment_index; segnum++)
700                         if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
701                                 return -2;
702         } else {
703                 for (segnum=0; segnum<=Highest_segment_index; segnum++) {
704                         int     objnum;
705
706                         objnum = exists_in_mine_2(segnum, objtype, objid, special);
707                         if (objnum != -1)
708                                 return -2;
709                 }
710         }
711
712         return -1;
713 }
714
715 //      -----------------------------------------------------------------------------
716 //      Return true if it happened, else return false.
717 int find_exit_segment(void)
718 {
719         int     i,j;
720
721         //      ---------- Find exit doors ----------
722         for (i=0; i<=Highest_segment_index; i++)
723                 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
724                         if (Segments[i].children[j] == -2) {
725                                 return i;
726                         }
727
728         return -1;
729 }
730
731 #define BUDDY_MARKER_TEXT_LEN   25
732
733 //      -----------------------------------------------------------------------------
734 void say_escort_goal(int goal_num)
735 {
736         if (Player_is_dead)
737                 return;
738
739         switch (goal_num) {
740                 case ESCORT_GOAL_BLUE_KEY:              buddy_message("Finding BLUE KEY");                      break;
741                 case ESCORT_GOAL_GOLD_KEY:              buddy_message("Finding YELLOW KEY");            break;
742                 case ESCORT_GOAL_RED_KEY:               buddy_message("Finding RED KEY");                       break;
743                 case ESCORT_GOAL_CONTROLCEN:    buddy_message("Finding REACTOR");                       break;
744                 case ESCORT_GOAL_EXIT:                  buddy_message("Finding EXIT");                          break;
745                 case ESCORT_GOAL_ENERGY:                buddy_message("Finding ENERGY");                                break;
746                 case ESCORT_GOAL_ENERGYCEN:     buddy_message("Finding ENERGY CENTER"); break;
747                 case ESCORT_GOAL_SHIELD:                buddy_message("Finding a SHIELD");                      break;
748                 case ESCORT_GOAL_POWERUP:               buddy_message("Finding a POWERUP");                     break;
749                 case ESCORT_GOAL_ROBOT:                 buddy_message("Finding a ROBOT");                       break;
750                 case ESCORT_GOAL_HOSTAGE:               buddy_message("Finding a HOSTAGE");                     break;
751                 case ESCORT_GOAL_SCRAM:                 buddy_message("Staying away...");                       break;
752                 case ESCORT_GOAL_BOSS:                  buddy_message("Finding BOSS robot");            break;
753                 case ESCORT_GOAL_PLAYER_SPEW:   buddy_message("Finding your powerups"); break;
754                 case ESCORT_GOAL_MARKER1:
755                 case ESCORT_GOAL_MARKER2:
756                 case ESCORT_GOAL_MARKER3:
757                 case ESCORT_GOAL_MARKER4:
758                 case ESCORT_GOAL_MARKER5:
759                 case ESCORT_GOAL_MARKER6:
760                 case ESCORT_GOAL_MARKER7:
761                 case ESCORT_GOAL_MARKER8:
762                 case ESCORT_GOAL_MARKER9:
763                         { char marker_text[BUDDY_MARKER_TEXT_LEN];
764                         strncpy(marker_text, MarkerMessage[goal_num-ESCORT_GOAL_MARKER1], BUDDY_MARKER_TEXT_LEN-1);
765                         marker_text[BUDDY_MARKER_TEXT_LEN-1] = 0;
766                         buddy_message("Finding marker %i: '%s'", goal_num-ESCORT_GOAL_MARKER1+1, marker_text);
767                         break;
768                         }
769         }
770 }
771
772 //      -----------------------------------------------------------------------------
773 void escort_create_path_to_goal(object *objp)
774 {
775         int     goal_seg = -1;
776         int                     objnum = objp-Objects;
777         ai_static       *aip = &objp->ctype.ai_info;
778         ai_local                *ailp = &Ai_local_info[objnum];
779
780         if (Escort_special_goal != -1)
781                 Escort_goal_object = Escort_special_goal;
782
783         Escort_kill_object = -1;
784
785         if (Looking_for_marker != -1) {
786
787                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_MARKER, Escort_goal_object-ESCORT_GOAL_MARKER1, -1);
788                 if (Escort_goal_index > -1)
789                         goal_seg = Objects[Escort_goal_index].segnum;
790         } else {
791                 switch (Escort_goal_object) {
792                         case ESCORT_GOAL_BLUE_KEY:
793                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1);
794                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
795                                 break;
796                         case ESCORT_GOAL_GOLD_KEY:
797                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1);
798                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
799                                 break;
800                         case ESCORT_GOAL_RED_KEY:
801                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_RED, -1);
802                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
803                                 break;
804                         case ESCORT_GOAL_CONTROLCEN:
805                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_CNTRLCEN, -1, -1);
806                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
807                                 break;
808                         case ESCORT_GOAL_EXIT:
809                         case ESCORT_GOAL_EXIT2:
810                                 goal_seg = find_exit_segment();
811                                 Escort_goal_index = goal_seg;
812                                 break;
813                         case ESCORT_GOAL_ENERGY:
814                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_ENERGY, -1);
815                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
816                                 break;
817                         case ESCORT_GOAL_ENERGYCEN:
818                                 goal_seg = exists_in_mine(objp->segnum, FUELCEN_CHECK, -1, -1);
819                                 Escort_goal_index = goal_seg;
820                                 break;
821                         case ESCORT_GOAL_SHIELD:
822                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_SHIELD_BOOST, -1);
823                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
824                                 break;
825                         case ESCORT_GOAL_POWERUP:
826                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, -1, -1);
827                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
828                                 break;
829                         case ESCORT_GOAL_ROBOT:
830                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, -1, -1);
831                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
832                                 break;
833                         case ESCORT_GOAL_HOSTAGE:
834                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_HOSTAGE, -1, -1);
835                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
836                                 break;
837                         case ESCORT_GOAL_PLAYER_SPEW:
838                                 Escort_goal_index = exists_in_mine(objp->segnum, -1, -1, ESCORT_GOAL_PLAYER_SPEW);
839                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
840                                 break;
841                         case ESCORT_GOAL_SCRAM:
842                                 goal_seg = -3;          //      Kinda a hack.
843                                 Escort_goal_index = goal_seg;
844                                 break;
845                         case ESCORT_GOAL_BOSS: {
846                                 int     boss_id;
847         
848                                 boss_id = get_boss_id();
849                                 Assert(boss_id != -1);
850                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, boss_id, -1);
851                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
852                                 break;
853                         }
854                         default:
855                                 Int3(); //      Oops, Illegal value in Escort_goal_object.
856                                 goal_seg = 0;
857                                 break;
858                 }
859         }
860
861         // -- mprintf((0, "Creating path from escort to goal #%i in segment #%i.\n", Escort_goal_object, goal_seg));
862         if ((Escort_goal_index < 0) && (Escort_goal_index != -3)) {     //      I apologize for this statement -- MK, 09/22/95
863                 if (Escort_goal_index == -1) {
864                         Last_buddy_message_time = 0;    //      Force this message to get through.
865                         buddy_message("No %s in mine.", Escort_goal_text[Escort_goal_object-1]);
866                         Looking_for_marker = -1;
867                 } else if (Escort_goal_index == -2) {
868                         Last_buddy_message_time = 0;    //      Force this message to get through.
869                         buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
870                         Looking_for_marker = -1;
871                 } else
872                         Int3();
873
874                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
875                 Escort_special_goal = -1;
876         } else {
877                 if (goal_seg == -3) {
878                         create_n_segment_path(objp, 16 + d_rand() * 16, -1);
879                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
880                 } else {
881                         create_path_to_segment(objp, goal_seg, Max_escort_length, 1);   //      MK!: Last parm (safety_flag) used to be 1!!
882                         if (aip->path_length > 3)
883                                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
884                         if ((aip->path_length > 0) && (Point_segs[aip->hide_index + aip->path_length - 1].segnum != goal_seg)) {
885                                 fix     dist_to_player;
886                                 Last_buddy_message_time = 0;    //      Force this message to get through.
887                                 buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
888                                 Looking_for_marker = -1;
889                                 Escort_goal_object = ESCORT_GOAL_SCRAM;
890                                 dist_to_player = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 100, WID_FLY_FLAG);
891                                 if (dist_to_player > MIN_ESCORT_DISTANCE)
892                                         create_path_to_player(objp, Max_escort_length, 1);      //      MK!: Last parm used to be 1!
893                                 else {
894                                         create_n_segment_path(objp, 8 + d_rand() * 8, -1);
895                                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
896                                 }
897                         }
898                 }
899
900                 ailp->mode = AIM_GOTO_OBJECT;
901
902                 say_escort_goal(Escort_goal_object);
903         }
904
905 }
906
907 //      -----------------------------------------------------------------------------
908 //      Escort robot chooses goal object based on player's keys, location.
909 //      Returns goal object.
910 int escort_set_goal_object(void)
911 {
912         if (Escort_special_goal != -1)
913                 return ESCORT_GOAL_UNSPECIFIED;
914         else if (!(ConsoleObject->flags & PLAYER_FLAGS_BLUE_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1) != -1))
915                 return ESCORT_GOAL_BLUE_KEY;
916         else if (!(ConsoleObject->flags & PLAYER_FLAGS_GOLD_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1) != -1))
917                 return ESCORT_GOAL_GOLD_KEY;
918         else if (!(ConsoleObject->flags & PLAYER_FLAGS_RED_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_RED, -1) != -1))
919                 return ESCORT_GOAL_RED_KEY;
920         else if (Control_center_destroyed == 0) {
921                 if (Num_boss_teleport_segs)
922                         return ESCORT_GOAL_BOSS;
923                 else
924                         return ESCORT_GOAL_CONTROLCEN;
925         } else
926                 return ESCORT_GOAL_EXIT;
927         
928 }
929
930 #define MAX_ESCORT_TIME_AWAY            (F1_0*4)
931
932 fix     Buddy_last_seen_player = 0, Buddy_last_player_path_created;
933
934 //      -----------------------------------------------------------------------------
935 int time_to_visit_player(object *objp, ai_local *ailp, ai_static *aip)
936 {
937         //      Note: This one has highest priority because, even if already going towards player,
938         //      might be necessary to create a new path, as player can move.
939         if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY)
940                 if (GameTime - Buddy_last_player_path_created > F1_0)
941                         return 1;
942
943         if (ailp->mode == AIM_GOTO_PLAYER)
944                 return 0;
945
946         if (objp->segnum == ConsoleObject->segnum)
947                 return 0;
948
949         if (aip->cur_path_index < aip->path_length/2)
950                 return 0;
951         
952         return 1;
953 }
954
955 int     Buddy_objnum;
956 int Buddy_dude_cheat;
957 fix     Last_come_back_message_time = 0;
958
959 fix     Buddy_last_missile_time;
960
961 //      -----------------------------------------------------------------------------
962 void bash_buddy_weapon_info(int weapon_objnum)
963 {
964         object  *objp = &Objects[weapon_objnum];
965
966         objp->ctype.laser_info.parent_num = ConsoleObject-Objects;
967         objp->ctype.laser_info.parent_type = OBJ_PLAYER;
968         objp->ctype.laser_info.parent_signature = ConsoleObject->signature;
969 }
970
971 //      -----------------------------------------------------------------------------
972 int maybe_buddy_fire_mega(int objnum)
973 {
974         object  *objp = &Objects[objnum];
975         object  *buddy_objp = &Objects[Buddy_objnum];
976         fix             dist, dot;
977         vms_vector      vec_to_robot;
978         int             weapon_objnum;
979
980         vm_vec_sub(&vec_to_robot, &buddy_objp->pos, &objp->pos);
981         dist = vm_vec_normalize_quick(&vec_to_robot);
982
983         if (dist > F1_0*100)
984                 return 0;
985
986         dot = vm_vec_dot(&vec_to_robot, &buddy_objp->orient.fvec);
987
988         if (dot < F1_0/2)
989                 return 0;
990
991         if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
992                 return 0;
993
994         if (Weapon_info[MEGA_ID].render_type == 0) {
995                 con_printf(CON_VERBOSE, "Buddy can't fire mega (shareware)\n");
996                 buddy_message("CLICK!");
997                 return 0;
998         }
999
1000         mprintf((0, "Buddy firing mega in frame %i\n", FrameCount));
1001
1002         buddy_message("GAHOOGA!");
1003
1004         weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, MEGA_ID, 1);
1005
1006         if (weapon_objnum != -1)
1007                 bash_buddy_weapon_info(weapon_objnum);
1008
1009         return 1;
1010 }
1011
1012 //-----------------------------------------------------------------------------
1013 int maybe_buddy_fire_smart(int objnum)
1014 {
1015         object  *objp = &Objects[objnum];
1016         object  *buddy_objp = &Objects[Buddy_objnum];
1017         fix             dist;
1018         int             weapon_objnum;
1019
1020         dist = vm_vec_dist_quick(&buddy_objp->pos, &objp->pos);
1021
1022         if (dist > F1_0*80)
1023                 return 0;
1024
1025         if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
1026                 return 0;
1027
1028         mprintf((0, "Buddy firing smart missile in frame %i\n", FrameCount));
1029
1030         buddy_message("WHAMMO!");
1031
1032         weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, SMART_ID, 1);
1033
1034         if (weapon_objnum != -1)
1035                 bash_buddy_weapon_info(weapon_objnum);
1036
1037         return 1;
1038 }
1039
1040 //      -----------------------------------------------------------------------------
1041 void do_buddy_dude_stuff(void)
1042 {
1043         int     i;
1044
1045         if (!ok_for_buddy_to_talk())
1046                 return;
1047
1048         if (Buddy_last_missile_time > GameTime)
1049                 Buddy_last_missile_time = 0;
1050
1051         if (Buddy_last_missile_time + F1_0*2 < GameTime) {
1052                 //      See if a robot potentially in view cone
1053                 for (i=0; i<=Highest_object_index; i++)
1054                         if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1055                                 if (maybe_buddy_fire_mega(i)) {
1056                                         Buddy_last_missile_time = GameTime;
1057                                         return;
1058                                 }
1059
1060                 //      See if a robot near enough that buddy should fire smart missile
1061                 for (i=0; i<=Highest_object_index; i++)
1062                         if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1063                                 if (maybe_buddy_fire_smart(i)) {
1064                                         Buddy_last_missile_time = GameTime;
1065                                         return;
1066                                 }
1067
1068         }
1069 }
1070
1071 //      -----------------------------------------------------------------------------
1072 //      Called every frame (or something).
1073 void do_escort_frame(object *objp, fix dist_to_player, int player_visibility)
1074 {
1075         int                     objnum = objp-Objects;
1076         ai_static       *aip = &objp->ctype.ai_info;
1077         ai_local                *ailp = &Ai_local_info[objnum];
1078
1079         Buddy_objnum = objp-Objects;
1080
1081         if (player_visibility) {
1082                 Buddy_last_seen_player = GameTime;
1083                 if (Players[Player_num].flags & PLAYER_FLAGS_HEADLIGHT_ON)      //      DAMN! MK, stupid bug, fixed 12/08/95, changed PLAYER_FLAGS_HEADLIGHT to PLAYER_FLAGS_HEADLIGHT_ON
1084                         if (f2i(Players[Player_num].energy) < 40)
1085                                 if ((f2i(Players[Player_num].energy)/2) & 2)
1086                                         if (!Player_is_dead)
1087                                                 buddy_message("Hey, your headlight's on!");
1088
1089         }
1090
1091         if (Buddy_dude_cheat)
1092                 do_buddy_dude_stuff();
1093
1094         if (Buddy_sorry_time + F1_0 > GameTime) {
1095                 Last_buddy_message_time = 0;    //      Force this message to get through.
1096                 if (Buddy_sorry_time < GameTime + F1_0*2)
1097                         buddy_message("Oops, sorry 'bout that...");
1098                 Buddy_sorry_time = -F1_0*2;
1099         }
1100
1101         //      If buddy not allowed to talk, then he is locked in his room.  Make him mostly do nothing unless you're nearby.
1102         if (!Buddy_allowed_to_talk)
1103                 if (dist_to_player > F1_0*100)
1104                         aip->SKIP_AI_COUNT = (F1_0/4)/FrameTime;
1105
1106         // -- mprintf((0, "%10s: Dist to player = %7.3f, segnum = %4i\n", mode_text[ailp->mode], f2fl(dist_to_player), objp->segnum));
1107
1108         //      AIM_WANDER has been co-opted for buddy behavior (didn't want to modify aistruct.h)
1109         //      It means the object has been told to get lost and has come to the end of its path.
1110         //      If the player is now visible, then create a path.
1111         if (ailp->mode == AIM_WANDER)
1112                 if (player_visibility) {
1113                         // -- mprintf((0, "Buddy: Going from wander to path following!\n"));
1114                         create_n_segment_path(objp, 16 + d_rand() * 16, -1);
1115                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1116                 }
1117
1118         if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1119                 if (player_visibility)
1120                         if (Escort_last_path_created + F1_0*3 < GameTime) {
1121                                 mprintf((0, "Frame %i: Buddy creating new scram path.\n", FrameCount));
1122                                 create_n_segment_path(objp, 10 + d_rand() * 16, ConsoleObject->segnum);
1123                                 Escort_last_path_created = GameTime;
1124                         }
1125
1126                 // -- Int3();
1127                 // -- mprintf((0, "Buddy: Seg = %3i, dist = %7.3f\n", objp->segnum, f2fl(dist_to_player)));
1128                 return;
1129         }
1130
1131         //      Force checking for new goal every 5 seconds, and create new path, if necessary.
1132         if (((Escort_special_goal != ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*5) < GameTime)) ||
1133                 ((Escort_special_goal == ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*15) < GameTime))) {
1134                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1135                 Escort_last_path_created = GameTime;
1136         }
1137
1138         if ((Escort_special_goal != ESCORT_GOAL_SCRAM) && time_to_visit_player(objp, ailp, aip)) {
1139                 int     max_len;
1140
1141                 Buddy_last_player_path_created = GameTime;
1142                 ailp->mode = AIM_GOTO_PLAYER;
1143                 if (!player_visibility) {
1144                         if ((Last_come_back_message_time + F1_0 < GameTime) || (Last_come_back_message_time > GameTime)) {
1145                                 buddy_message("Coming back to get you.");
1146                                 Last_come_back_message_time = GameTime;
1147                         }
1148                 }
1149                 //      No point in Buddy creating very long path if he's not allowed to talk.  Really kills framerate.
1150                 max_len = Max_escort_length;
1151                 if (!Buddy_allowed_to_talk)
1152                         max_len = 3;
1153                 create_path_to_player(objp, max_len, 1);        //      MK!: Last parm used to be 1!
1154                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1155                 // -- mprintf((0, "Creating path to player, length = %i\n", aip->path_length));
1156                 ailp->mode = AIM_GOTO_PLAYER;
1157         }       else if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY) {
1158                 //      This is to prevent buddy from looking for a goal, which he will do because we only allow path creation once/second.
1159                 return;
1160         } else if ((ailp->mode == AIM_GOTO_PLAYER) && (dist_to_player < MIN_ESCORT_DISTANCE)) {
1161                 Escort_goal_object = escort_set_goal_object();
1162                 ailp->mode = AIM_GOTO_OBJECT;           //      May look stupid to be before path creation, but ai_door_is_openable uses mode to determine what doors can be got through
1163                 escort_create_path_to_goal(objp);
1164                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1165                 // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1166                 if (aip->path_length < 3) {
1167                         create_n_segment_path(objp, 5, Believed_player_seg);
1168                         // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1169                 }
1170                 ailp->mode = AIM_GOTO_OBJECT;
1171         } else if (Escort_goal_object == ESCORT_GOAL_UNSPECIFIED) {
1172                 if ((ailp->mode != AIM_GOTO_PLAYER) || (dist_to_player < MIN_ESCORT_DISTANCE)) {
1173                         Escort_goal_object = escort_set_goal_object();
1174                         ailp->mode = AIM_GOTO_OBJECT;           //      May look stupid to be before path creation, but ai_door_is_openable uses mode to determine what doors can be got through
1175                         escort_create_path_to_goal(objp);
1176                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1177                         // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1178                         if (aip->path_length < 3) {
1179                                 create_n_segment_path(objp, 5, Believed_player_seg);
1180                                 // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1181                         }
1182                         ailp->mode = AIM_GOTO_OBJECT;
1183                 }
1184         } else
1185                 ; // mprintf((0, "!"));
1186
1187 }
1188
1189 void invalidate_escort_goal(void)
1190 {
1191         Escort_goal_object = -1;
1192 }
1193
1194 //      -------------------------------------------------------------------------------------------------
1195 void do_snipe_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1196 {
1197         int                     objnum = objp-Objects;
1198         ai_local                *ailp = &Ai_local_info[objnum];
1199         fix                     connected_distance;
1200
1201         if (dist_to_player > F1_0*500)
1202                 return;
1203
1204         // -- mprintf((0, "Mode: %10s, Dist: %7.3f\n", mode_text[ailp->mode], f2fl(dist_to_player)));
1205
1206         switch (ailp->mode) {
1207                 case AIM_SNIPE_WAIT:
1208                         if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1209                                 return;
1210
1211                         ailp->next_action_time = SNIPE_WAIT_TIME;
1212
1213                         connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1214                         if (connected_distance < F1_0*500) {
1215                                 // -- mprintf((0, "Object #%i entering attack mode.\n", objnum));
1216                                 create_path_to_player(objp, 30, 1);
1217                                 ailp->mode = AIM_SNIPE_ATTACK;
1218                                 ailp->next_action_time = SNIPE_ATTACK_TIME;     //      have up to 10 seconds to find player.
1219                         }
1220                         break;
1221
1222                 case AIM_SNIPE_RETREAT:
1223                 case AIM_SNIPE_RETREAT_BACKWARDS:
1224                         if (ailp->next_action_time < 0) {
1225                                 ailp->mode = AIM_SNIPE_WAIT;
1226                                 ailp->next_action_time = SNIPE_WAIT_TIME;
1227                                 // -- mprintf((0, "Object #%i going from retreat to wait.\n", objnum));
1228                         } else if ((player_visibility == 0) || (ailp->next_action_time > SNIPE_ABORT_RETREAT_TIME)) {
1229                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1230                                 ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1231                         } else {
1232                                 // -- mprintf((0, "Object #%i going from retreat to fire.\n", objnum));
1233                                 ailp->mode = AIM_SNIPE_FIRE;
1234                                 ailp->next_action_time = SNIPE_FIRE_TIME/2;
1235                         }
1236                         break;
1237
1238                 case AIM_SNIPE_ATTACK:
1239                         if (ailp->next_action_time < 0) {
1240                                 // -- mprintf((0, "Object #%i timed out from attack to retreat mode.\n", objnum));
1241                                 ailp->mode = AIM_SNIPE_RETREAT;
1242                                 ailp->next_action_time = SNIPE_WAIT_TIME;
1243                         } else {
1244                                 // -- mprintf((0, "Object #%i attacking: visibility = %i\n", player_visibility));
1245                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1246                                 if (player_visibility) {
1247                                         ailp->mode = AIM_SNIPE_FIRE;
1248                                         ailp->next_action_time = SNIPE_FIRE_TIME;
1249                                 } else
1250                                         ailp->mode = AIM_SNIPE_ATTACK;
1251                         }
1252                         break;
1253
1254                 case AIM_SNIPE_FIRE:
1255                         if (ailp->next_action_time < 0) {
1256                                 ai_static       *aip = &objp->ctype.ai_info;
1257                                 // -- mprintf((0, "Object #%i going from fire to retreat.\n", objnum));
1258                                 create_n_segment_path(objp, 10 + d_rand()/2048, ConsoleObject->segnum);
1259                                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1260                                 if (d_rand() < 8192)
1261                                         ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1262                                 else
1263                                         ailp->mode = AIM_SNIPE_RETREAT;
1264                                 ailp->next_action_time = SNIPE_RETREAT_TIME;
1265                         } else {
1266                         }
1267                         break;
1268
1269                 default:
1270                         Int3(); //      Oops, illegal mode for snipe behavior.
1271                         ailp->mode = AIM_SNIPE_ATTACK;
1272                         ailp->next_action_time = F1_0;
1273                         break;
1274         }
1275
1276 }
1277
1278 #define THIEF_DEPTH     20
1279
1280 extern int pick_connected_segment(object *objp, int max_depth);
1281
1282 //      ------------------------------------------------------------------------------------------------------
1283 //      Choose segment to recreate thief in.
1284 int choose_thief_recreation_segment(void)
1285 {
1286         int     segnum = -1;
1287         int     cur_drop_depth;
1288
1289         cur_drop_depth = THIEF_DEPTH;
1290
1291         while ((segnum == -1) && (cur_drop_depth > THIEF_DEPTH/2)) {
1292                 segnum = pick_connected_segment(&Objects[Players[Player_num].objnum], cur_drop_depth);
1293                 if (Segment2s[segnum].special == SEGMENT_IS_CONTROLCEN)
1294                         segnum = -1;
1295                 cur_drop_depth--;
1296         }
1297
1298         if (segnum == -1) {
1299                 mprintf((1, "Warning: Unable to find a connected segment for thief recreation.\n"));
1300                 return (d_rand() * Highest_segment_index) >> 15;
1301         } else
1302                 return segnum;
1303
1304 }
1305
1306 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
1307
1308 fix     Re_init_thief_time = 0x3f000000;
1309
1310 //      ----------------------------------------------------------------------
1311 void recreate_thief(object *objp)
1312 {
1313         int                     segnum;
1314         vms_vector      center_point;
1315         object          *new_obj;
1316
1317         segnum = choose_thief_recreation_segment();
1318         compute_segment_center(&center_point, &Segments[segnum]);
1319
1320         new_obj = create_morph_robot( &Segments[segnum], &center_point, objp->id);
1321         init_ai_object(new_obj-Objects, AIB_SNIPE, -1);
1322         Re_init_thief_time = GameTime + F1_0*10;                //      In 10 seconds, re-initialize thief.
1323 }
1324
1325 //      ----------------------------------------------------------------------------
1326 #define THIEF_ATTACK_TIME               (F1_0*10)
1327
1328 fix     Thief_wait_times[NDL] = {F1_0*30, F1_0*25, F1_0*20, F1_0*15, F1_0*10};
1329
1330 //      -------------------------------------------------------------------------------------------------
1331 void do_thief_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1332 {
1333         int                     objnum = objp-Objects;
1334         ai_local                *ailp = &Ai_local_info[objnum];
1335         fix                     connected_distance;
1336
1337         // -- mprintf((0, "%10s: Action Time: %7.3f\n", mode_text[ailp->mode], f2fl(ailp->next_action_time)));
1338
1339         if ((Current_level_num < 0) && (Re_init_thief_time < GameTime)) {
1340                 if (Re_init_thief_time > GameTime - F1_0*2)
1341                         init_thief_for_level();
1342                 Re_init_thief_time = 0x3f000000;
1343         }
1344
1345         if ((dist_to_player > F1_0*500) && (ailp->next_action_time > 0))
1346                 return;
1347
1348         if (Player_is_dead)
1349                 ailp->mode = AIM_THIEF_RETREAT;
1350
1351         switch (ailp->mode) {
1352                 case AIM_THIEF_WAIT:
1353                         // -- mprintf((0, "WAIT\n"));
1354
1355                         if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1356                                 ailp->player_awareness_type = 0;
1357                                 // -- mprintf((0, "Thief: Awareness = %i ", ailp->player_awareness_type));
1358
1359                                 // -- mprintf((0, "ATTACK\n"));
1360                                 create_path_to_player(objp, 30, 1);
1361                                 ailp->mode = AIM_THIEF_ATTACK;
1362                                 ailp->next_action_time = THIEF_ATTACK_TIME/2;
1363                                 return;
1364                         } else if (player_visibility) {
1365                                 // -- mprintf((0, "RETREAT\n"));
1366                                 create_n_segment_path(objp, 15, ConsoleObject->segnum);
1367                                 ailp->mode = AIM_THIEF_RETREAT;
1368                                 return;
1369                         }
1370
1371                         if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1372                                 return;
1373
1374                         ailp->next_action_time = Thief_wait_times[Difficulty_level]/2;
1375
1376                         connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1377                         if (connected_distance < F1_0*500) {
1378                                 // -- mprintf((0, "Thief creating path to player.\n", objnum));
1379                                 create_path_to_player(objp, 30, 1);
1380                                 ailp->mode = AIM_THIEF_ATTACK;
1381                                 ailp->next_action_time = THIEF_ATTACK_TIME;     //      have up to 10 seconds to find player.
1382                         }
1383
1384                         break;
1385
1386                 case AIM_THIEF_RETREAT:
1387                         // -- mprintf((0, "RETREAT\n"));
1388
1389                         if (ailp->next_action_time < 0) {
1390                                 ailp->mode = AIM_THIEF_WAIT;
1391                                 ailp->next_action_time = Thief_wait_times[Difficulty_level];
1392                         } else if ((dist_to_player < F1_0*100) || player_visibility || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1393                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1394                                 if ((dist_to_player < F1_0*100) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1395                                         ai_static       *aip = &objp->ctype.ai_info;
1396                                         if (((aip->cur_path_index <=1) && (aip->PATH_DIR == -1)) || ((aip->cur_path_index >= aip->path_length-1) && (aip->PATH_DIR == 1))) {
1397                                                 ailp->player_awareness_type = 0;
1398                                                 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1399
1400                                                 //      If path is real short, try again, allowing to go through player's segment
1401                                                 if (aip->path_length < 4) {
1402                                                         // -- mprintf((0, "Thief is cornered.  Willing to fly through player.\n"));
1403                                                         create_n_segment_path(objp, 10, -1);
1404                                                 } else if (objp->shields* 4 < Robot_info[objp->id].strength) {
1405                                                         //      If robot really low on hits, will run through player with even longer path
1406                                                         if (aip->path_length < 8) {
1407                                                                 create_n_segment_path(objp, 10, -1);
1408                                                         }
1409                                                 }
1410
1411                                                 ailp->mode = AIM_THIEF_RETREAT;
1412                                                 // -- mprintf((0, "Thief creating new RETREAT path.\n"));
1413                                         }
1414                                 } else
1415                                         ailp->mode = AIM_THIEF_RETREAT;
1416
1417                         }
1418
1419                         break;
1420
1421                 //      This means the thief goes from wherever he is to the player.
1422                 //      Note: When thief successfully steals something, his action time is forced negative and his mode is changed
1423                 //                      to retreat to get him out of attack mode.
1424                 case AIM_THIEF_ATTACK:
1425                         // -- mprintf((0, "ATTACK\n"));
1426
1427                         if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1428                                 ailp->player_awareness_type = 0;
1429                                 if (d_rand() > 8192) {
1430                                         // --- mprintf((0, "RETREAT!!\n"));
1431                                         create_n_segment_path(objp, 10, ConsoleObject->segnum);
1432                                         Ai_local_info[objp-Objects].next_action_time = Thief_wait_times[Difficulty_level]/2;
1433                                         Ai_local_info[objp-Objects].mode = AIM_THIEF_RETREAT;
1434                                 }
1435                         } else if (ailp->next_action_time < 0) {
1436                                 //      This forces him to create a new path every second.
1437                                 ailp->next_action_time = F1_0;
1438                                 create_path_to_player(objp, 100, 0);
1439                                 ailp->mode = AIM_THIEF_ATTACK;
1440                                 // -- mprintf((0, "Creating path to player.\n"));
1441                         } else {
1442                                 if (player_visibility && (dist_to_player < F1_0*100)) {
1443                                         //      If the player is close to looking at the thief, thief shall run away.
1444                                         //      No more stupid thief trying to sneak up on you when you're looking right at him!
1445                                         if (dist_to_player > F1_0*60) {
1446                                                 fix     dot = vm_vec_dot(vec_to_player, &ConsoleObject->orient.fvec);
1447                                                 if (dot < -F1_0/2) {    //      Looking at least towards thief, so thief will run!
1448                                                         create_n_segment_path(objp, 10, ConsoleObject->segnum);
1449                                                         Ai_local_info[objp-Objects].next_action_time = Thief_wait_times[Difficulty_level]/2;
1450                                                         Ai_local_info[objp-Objects].mode = AIM_THIEF_RETREAT;
1451                                                 }
1452                                         } 
1453                                         ai_turn_towards_vector(vec_to_player, objp, F1_0/4);
1454                                         move_towards_player(objp, vec_to_player);
1455                                 } else {
1456                                         ai_static       *aip = &objp->ctype.ai_info;
1457                                         //      If path length == 0, then he will keep trying to create path, but he is probably stuck in his closet.
1458                                         if ((aip->path_length > 1) || ((FrameCount & 0x0f) == 0)) {
1459                                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1460                                                 ailp->mode = AIM_THIEF_ATTACK;
1461                                         }
1462                                 }
1463                         }
1464                         break;
1465
1466                 default:
1467                         mprintf ((0,"Thief mode (broken) = %d\n",ailp->mode));
1468                         // -- Int3();   //      Oops, illegal mode for thief behavior.
1469                         ailp->mode = AIM_THIEF_ATTACK;
1470                         ailp->next_action_time = F1_0;
1471                         break;
1472         }
1473
1474 }
1475
1476 //      ----------------------------------------------------------------------------
1477 //      Return true if this item (whose presence is indicated by Players[player_num].flags) gets stolen.
1478 int maybe_steal_flag_item(int player_num, int flagval)
1479 {
1480         if (Players[player_num].flags & flagval) {
1481                 if (d_rand() < THIEF_PROBABILITY) {
1482                         int     powerup_index=-1;
1483                         Players[player_num].flags &= (~flagval);
1484                         // -- mprintf((0, "You lost your %4x capability!\n", flagval));
1485                         switch (flagval) {
1486                                 case PLAYER_FLAGS_INVULNERABLE:
1487                                         powerup_index = POW_INVULNERABILITY;
1488                                         thief_message("Invulnerability stolen!");
1489                                         break;
1490                                 case PLAYER_FLAGS_CLOAKED:
1491                                         powerup_index = POW_CLOAK;
1492                                         thief_message("Cloak stolen!");
1493                                         break;
1494                                 case PLAYER_FLAGS_MAP_ALL:
1495                                         powerup_index = POW_FULL_MAP;
1496                                         thief_message("Full map stolen!");
1497                                         break;
1498                                 case PLAYER_FLAGS_QUAD_LASERS:
1499                                         powerup_index = POW_QUAD_FIRE;
1500                                         thief_message("Quad lasers stolen!");
1501                                         break;
1502                                 case PLAYER_FLAGS_AFTERBURNER:
1503                                         powerup_index = POW_AFTERBURNER;
1504                                         thief_message("Afterburner stolen!");
1505                                         break;
1506 // --                           case PLAYER_FLAGS_AMMO_RACK:
1507 // --                                   powerup_index = POW_AMMO_RACK;
1508 // --                                   thief_message("Ammo Rack stolen!");
1509 // --                                   break;
1510                                 case PLAYER_FLAGS_CONVERTER:
1511                                         powerup_index = POW_CONVERTER;
1512                                         thief_message("Converter stolen!");
1513                                         break;
1514                                 case PLAYER_FLAGS_HEADLIGHT:
1515                                         powerup_index = POW_HEADLIGHT;
1516                                         thief_message("Headlight stolen!");
1517                                    Players[Player_num].flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1518                                         break;
1519                         }
1520                         Assert(powerup_index != -1);
1521                         Stolen_items[Stolen_item_index] = powerup_index;
1522
1523                         digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1524                         return 1;
1525                 }
1526         }
1527
1528         return 0;
1529 }
1530
1531 //      ----------------------------------------------------------------------------
1532 int maybe_steal_secondary_weapon(int player_num, int weapon_num)
1533 {
1534         if ((Players[player_num].secondary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].secondary_ammo[weapon_num])
1535                 if (d_rand() < THIEF_PROBABILITY) {
1536                         if (weapon_num == PROXIMITY_INDEX)
1537                                 if (d_rand() > 8192)            //      Come in groups of 4, only add 1/4 of time.
1538                                         return 0;
1539                         Players[player_num].secondary_ammo[weapon_num]--;
1540
1541                         //      Smart mines and proxbombs don't get dropped because they only come in 4 packs.
1542                         if ((weapon_num != PROXIMITY_INDEX) && (weapon_num != SMART_MINE_INDEX)) {
1543                                 Stolen_items[Stolen_item_index] = Secondary_weapon_to_powerup[weapon_num];
1544                         }
1545
1546                         thief_message("%s stolen!", Text_string[114+weapon_num]);               //      Danger! Danger! Use of literal!  Danger!
1547                         if (Players[Player_num].secondary_ammo[weapon_num] == 0)
1548                                 auto_select_weapon(1);
1549
1550                         // -- compress_stolen_items();
1551                         digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1552                         return 1;
1553                 }
1554
1555         return 0;
1556 }
1557
1558 //      ----------------------------------------------------------------------------
1559 int maybe_steal_primary_weapon(int player_num, int weapon_num)
1560 {
1561         if ((Players[player_num].primary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].primary_ammo[weapon_num]) {
1562                 if (d_rand() < THIEF_PROBABILITY) {
1563                         if (weapon_num == 0) {
1564                                 if (Players[player_num].laser_level > 0) {
1565                                         if (Players[player_num].laser_level > 3) {
1566                                                 Stolen_items[Stolen_item_index] = POW_SUPER_LASER;
1567                                         } else {
1568                                                 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1569                                         }
1570                                         thief_message("%s level decreased!", Text_string[104+weapon_num]);              //      Danger! Danger! Use of literal!  Danger!
1571                                         Players[player_num].laser_level--;
1572                                         digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1573                                         return 1;
1574                                 }
1575                         } else if (Players[player_num].primary_weapon_flags & (1 << weapon_num)) {
1576                                 Players[player_num].primary_weapon_flags &= ~(1 << weapon_num);
1577                                 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1578
1579                                 thief_message("%s stolen!", Text_string[104+weapon_num]);               //      Danger! Danger! Use of literal!  Danger!
1580                                 auto_select_weapon(0);
1581                                 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1582                                 return 1;
1583                         }
1584                 }
1585         }
1586
1587         return 0;
1588 }
1589
1590
1591
1592 //      ----------------------------------------------------------------------------
1593 //      Called for a thief-type robot.
1594 //      If a item successfully stolen, returns true, else returns false.
1595 //      If a wapon successfully stolen, do everything, removing it from player,
1596 //      updating Stolen_items information, deselecting, etc.
1597 int attempt_to_steal_item_3(object *objp, int player_num)
1598 {
1599         int     i;
1600
1601         if (Ai_local_info[objp-Objects].mode != AIM_THIEF_ATTACK)
1602                 return 0;
1603
1604         //      First, try to steal equipped items.
1605
1606         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1607                 return 1;
1608
1609         //      If primary weapon = laser, first try to rip away those nasty quad lasers!
1610         if (Primary_weapon == 0)
1611                 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1612                         return 1;
1613
1614         //      Makes it more likely to steal primary than secondary.
1615         for (i=0; i<2; i++)
1616                 if (maybe_steal_primary_weapon(player_num, Primary_weapon))
1617                         return 1;
1618
1619         if (maybe_steal_secondary_weapon(player_num, Secondary_weapon))
1620                 return 1;
1621
1622         //      See what the player has and try to snag something.
1623         //      Try best things first.
1624         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1625                 return 1;
1626         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CLOAKED))
1627                 return 1;
1628         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1629                 return 1;
1630         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AFTERBURNER))
1631                 return 1;
1632         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CONVERTER))
1633                 return 1;
1634 // --   if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AMMO_RACK))  //      Can't steal because what if have too many items, say 15 homing missiles?
1635 // --           return 1;
1636         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_HEADLIGHT))
1637                 return 1;
1638         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_MAP_ALL))
1639                 return 1;
1640
1641         for (i=MAX_SECONDARY_WEAPONS-1; i>=0; i--) {
1642                 if (maybe_steal_primary_weapon(player_num, i))
1643                         return 1;
1644                 if (maybe_steal_secondary_weapon(player_num, i))
1645                         return 1;
1646         }
1647
1648         return 0;
1649 }
1650
1651 //      ----------------------------------------------------------------------------
1652 int attempt_to_steal_item_2(object *objp, int player_num)
1653 {
1654         int     rval;
1655
1656         rval = attempt_to_steal_item_3(objp, player_num);
1657
1658         if (rval) {
1659                 Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1660                 if (d_rand() > 20000)   //      Occasionally, boost the value again
1661                         Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1662         }
1663
1664         return rval;
1665 }
1666
1667 //      ----------------------------------------------------------------------------
1668 //      Called for a thief-type robot.
1669 //      If a item successfully stolen, returns true, else returns false.
1670 //      If a wapon successfully stolen, do everything, removing it from player,
1671 //      updating Stolen_items information, deselecting, etc.
1672 int attempt_to_steal_item(object *objp, int player_num)
1673 {
1674         int     i;
1675         int     rval = 0;
1676
1677         if (objp->ctype.ai_info.dying_start_time)
1678                 return 0;
1679
1680         rval += attempt_to_steal_item_2(objp, player_num);
1681
1682         for (i=0; i<3; i++) {
1683                 if (!rval || (d_rand() < 11000)) {      //      about 1/3 of time, steal another item
1684                         rval += attempt_to_steal_item_2(objp, player_num);
1685                 } else
1686                         break;
1687         }
1688         // -- mprintf((0, "%i items were stolen!\n", rval));
1689
1690         create_n_segment_path(objp, 10, ConsoleObject->segnum);
1691         Ai_local_info[objp-Objects].next_action_time = Thief_wait_times[Difficulty_level]/2;
1692         Ai_local_info[objp-Objects].mode = AIM_THIEF_RETREAT;
1693         if (rval) {
1694                 PALETTE_FLASH_ADD(30, 15, -20);
1695                 update_laser_weapon_info();
1696 //              digi_link_sound_to_pos( SOUND_NASTY_ROBOT_HIT_1, objp->segnum, 0, &objp->pos, 0 , DEFAULT_ROBOT_SOUND_VOLUME);
1697 //      I removed this to make the "steal sound" more obvious -AP
1698 #ifdef NETWORK
1699                 if (Game_mode & GM_NETWORK)
1700                  multi_send_stolen_items();
1701 #endif
1702         }
1703         return rval;
1704 }
1705
1706 // --------------------------------------------------------------------------------------------------------------
1707 //      Indicate no items have been stolen.
1708 void init_thief_for_level(void)
1709 {
1710         int     i;
1711
1712         for (i=0; i<MAX_STOLEN_ITEMS; i++)
1713                 Stolen_items[i] = 255;
1714
1715         Assert (MAX_STOLEN_ITEMS >= 3*2);       //      Oops!  Loop below will overwrite memory!
1716   
1717    if (!(Game_mode & GM_MULTI))    
1718                 for (i=0; i<3; i++) {
1719                         Stolen_items[2*i] = POW_SHIELD_BOOST;
1720                         Stolen_items[2*i+1] = POW_ENERGY;
1721                 }
1722
1723         Stolen_item_index = 0;
1724 }
1725
1726 // --------------------------------------------------------------------------------------------------------------
1727 void drop_stolen_items(object *objp)
1728 {
1729         int     i;
1730
1731         mprintf ((0,"Dropping thief items!\n"));
1732
1733         // -- compress_stolen_items();
1734
1735         for (i=0; i<MAX_STOLEN_ITEMS; i++) {
1736                 if (Stolen_items[i] != 255)
1737                         drop_powerup(OBJ_POWERUP, Stolen_items[i], 1, &objp->mtype.phys_info.velocity, &objp->pos, objp->segnum);
1738                 Stolen_items[i] = 255;
1739         }
1740
1741 }
1742
1743 // --------------------------------------------------------------------------------------------------------------
1744 void do_escort_menu(void)
1745 {
1746         int     i;
1747         char    msg[300];
1748         int     paused;
1749         int     key;
1750         int     next_goal;
1751         char    goal_str[32], tstr[32];
1752
1753         if (Game_mode & GM_MULTI) {
1754                 HUD_init_message("No Guide-Bot in Multiplayer!");
1755                 return;
1756         }
1757
1758         for (i=0; i<=Highest_object_index; i++) {
1759                 if (Objects[i].type == OBJ_ROBOT)
1760                         if (Robot_info[Objects[i].id].companion)
1761                                 break;
1762         }
1763
1764         if (i > Highest_object_index) {
1765
1766                 HUD_init_message("No Guide-Bot present in mine!");
1767
1768                 #ifndef NDEBUG
1769                 //      If no buddy bot, create one!
1770                 HUD_init_message("Debug Version: Creating Guide-Bot!");
1771                 create_buddy_bot();
1772                 #else
1773                 return;
1774                 #endif
1775         }
1776
1777         ok_for_buddy_to_talk(); //      Needed here or we might not know buddy can talk when he can.
1778
1779         if (!Buddy_allowed_to_talk) {
1780                 HUD_init_message("%s has not been released",guidebot_name);
1781                 return;
1782         }
1783
1784         digi_pause_digi_sounds();
1785         stop_time();
1786
1787         palette_save();
1788         apply_modified_palette();
1789         reset_palette_add();
1790
1791         game_flush_inputs();
1792
1793         paused = 1;
1794
1795 //      set_screen_mode( SCREEN_MENU );
1796         set_popup_screen();
1797         gr_palette_load( gr_palette );
1798
1799         //      This prevents the buddy from coming back if you've told him to scram.
1800         //      If we don't set next_goal, we get garbage there.
1801         if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1802                 Escort_special_goal = -1;       //      Else setting next goal might fail.
1803                 next_goal = escort_set_goal_object();
1804                 Escort_special_goal = ESCORT_GOAL_SCRAM;
1805         } else {
1806                 Escort_special_goal = -1;       //      Else setting next goal might fail.
1807                 next_goal = escort_set_goal_object();
1808         }
1809
1810         switch (next_goal) {
1811         #ifndef NDEBUG
1812                 case ESCORT_GOAL_UNSPECIFIED:
1813                         Int3();
1814                         sprintf(goal_str, "ERROR");
1815                         break;
1816         #endif
1817                         
1818                 case ESCORT_GOAL_BLUE_KEY:
1819                         sprintf(goal_str, "blue key");
1820                         break;
1821                 case ESCORT_GOAL_GOLD_KEY:
1822                         sprintf(goal_str, "yellow key");
1823                         break;
1824                 case ESCORT_GOAL_RED_KEY:
1825                         sprintf(goal_str, "red key");
1826                         break;
1827                 case ESCORT_GOAL_CONTROLCEN:
1828                         sprintf(goal_str, "reactor");
1829                         break;
1830                 case ESCORT_GOAL_BOSS:
1831                         sprintf(goal_str, "boss");
1832                         break;
1833                 case ESCORT_GOAL_EXIT:
1834                         sprintf(goal_str, "exit");
1835                         break;
1836                 case ESCORT_GOAL_MARKER1:
1837                 case ESCORT_GOAL_MARKER2:
1838                 case ESCORT_GOAL_MARKER3:
1839                 case ESCORT_GOAL_MARKER4:
1840                 case ESCORT_GOAL_MARKER5:
1841                 case ESCORT_GOAL_MARKER6:
1842                 case ESCORT_GOAL_MARKER7:
1843                 case ESCORT_GOAL_MARKER8:
1844                 case ESCORT_GOAL_MARKER9:
1845                         sprintf(goal_str, "marker %i", next_goal-ESCORT_GOAL_MARKER1+1);
1846                         break;
1847
1848         }
1849                         
1850         if (!Buddy_messages_suppressed)
1851                 sprintf(tstr, "Suppress");
1852         else
1853                 sprintf(tstr, "Enable");
1854
1855         sprintf(msg,    "Select Guide-Bot Command:\n\n"
1856                                                 "0.  Next Goal: %s" CC_LSPACING_S "3\n"
1857                                                 "\x84.  Find Energy Powerup" CC_LSPACING_S "3\n"
1858                                                 "2.  Find Energy Center" CC_LSPACING_S "3\n"
1859                                                 "3.  Find Shield Powerup" CC_LSPACING_S "3\n"
1860                                                 "4.  Find Any Powerup" CC_LSPACING_S "3\n"
1861                                                 "5.  Find a Robot" CC_LSPACING_S "3\n"
1862                                                 "6.  Find a Hostage" CC_LSPACING_S "3\n"
1863                                                 "7.  Stay Away From Me" CC_LSPACING_S "3\n"
1864                                                 "8.  Find My Powerups" CC_LSPACING_S "3\n"
1865                                                 "9.  Find the exit\n\n"
1866                                                 "T.  %s Messages\n"
1867                                                 // -- "9.       Find the exit" CC_LSPACING_S "3\n"
1868                                 , goal_str, tstr);
1869
1870         show_escort_menu(msg);          //TXT_PAUSE);
1871
1872         while (paused) {
1873         #ifdef WINDOWS
1874                 while (!(key = key_inkey()))
1875                 {
1876                         MSG wmsg;
1877                         DoMessageStuff(&wmsg);
1878                 }
1879         #else
1880                 key = key_getch();
1881         #endif
1882
1883                 switch (key) {
1884                         case KEY_0:
1885                         case KEY_1:
1886                         case KEY_2:
1887                         case KEY_3:
1888                         case KEY_4:
1889                         case KEY_5:
1890                         case KEY_6:
1891                         case KEY_7:
1892                         case KEY_8:
1893                         case KEY_9:
1894                                 Looking_for_marker = -1;
1895                                 Last_buddy_key = -1;
1896                                 set_escort_special_goal(key);
1897                                 Last_buddy_key = -1;
1898                                 paused = 0;
1899                                 break;
1900
1901                         case KEY_ESC:
1902                         case KEY_ENTER:
1903                                 clear_boxed_message();
1904                                 paused=0;
1905                                 break;
1906
1907 //--10/08/95-- Screwed up font, background.  Why needed, anyway?
1908 //--10/08/95--                  case KEY_F1:
1909 //--10/08/95--                          clear_boxed_message();
1910 //--10/08/95--                          do_show_help();
1911 //--10/08/95--                          show_boxed_message(msg);
1912 //--10/08/95--                          break;
1913
1914                         case KEY_PRINT_SCREEN:
1915                                 save_screen_shot(0);
1916                                 break;
1917
1918                         #ifndef RELEASE
1919                         case KEY_BACKSP: Int3(); break;
1920                         #endif
1921
1922                         case KEY_T: {
1923                                 char    msg[32];
1924                                 int     temp;
1925
1926                                 temp = !Buddy_messages_suppressed;
1927
1928                                 if (temp)
1929                                         strcpy(msg, "suppressed");
1930                                 else
1931                                         strcpy(msg, "enabled");
1932
1933                                 Buddy_messages_suppressed = 1;
1934                                 buddy_message("Messages %s.", msg);
1935
1936                                 Buddy_messages_suppressed = temp;
1937
1938                                 paused = 0;
1939                                 break;
1940                         }
1941
1942                         default:
1943                                 break;
1944
1945                 }
1946
1947         }
1948
1949         game_flush_inputs();
1950
1951         palette_restore();
1952
1953         start_time();
1954         digi_resume_digi_sounds();
1955
1956 }
1957
1958 //      -------------------------------------------------------------------------------
1959 //      Show the Buddy menu!
1960 void show_escort_menu(char *msg)
1961 {       
1962         int     w,h,aw;
1963         int     x,y;
1964
1965
1966         WINDOS(
1967                 dd_gr_set_current_canvas(&dd_VR_screen_pages[0]),
1968                 gr_set_current_canvas(&VR_screen_pages[0])
1969         );
1970
1971         gr_set_curfont( GAME_FONT );
1972
1973         gr_get_string_size(msg,&w,&h,&aw);
1974
1975         x = (grd_curscreen->sc_w-w)/2;
1976         y = (grd_curscreen->sc_h-h)/4;
1977
1978         gr_set_fontcolor( gr_getcolor(0, 28, 0), -1 );
1979    
1980    PA_DFX (pa_set_frontbuffer_current());
1981         PA_DFX (nm_draw_background(x-15,y-15,x+w+15-1,y+h+15-1));
1982    PA_DFX (pa_set_backbuffer_current());
1983    nm_draw_background(x-15,y-15,x+w+15-1,y+h+15-1);
1984
1985 WIN(DDGRLOCK(dd_grd_curcanv));\
1986         PA_DFX (pa_set_frontbuffer_current());
1987         PA_DFX (gr_ustring( x, y, msg ));
1988         PA_DFX (pa_set_backbuffer_current());
1989         gr_ustring( x, y, msg );
1990 WIN(DDGRUNLOCK(dd_grd_curcanv));
1991         gr_update();
1992
1993         reset_cockpit();
1994 }