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